Compare commits

..
Author SHA1 Message Date
ohif-bot ff5ead6284 chore(version): Update package versions [skip ci] 2025-12-03 20:40:56 +00:00
ohif-bot 58c2cc70cf chore(version): version.json [skip ci] 2025-12-03 20:40:48 +00:00
Belbin-GKandtrenser-belbin 1aa4431121 fix: skip VOI sync for empty viewports (#5599)
Co-authored-by: trenser-belbin <belbin.kunjumon@prenuvo.com>
2025-12-03 15:36:30 -05:00
ohif-bot 4e5d5a7701 chore(version): Update package versions [skip ci] 2025-12-02 18:05:56 +00:00
ohif-bot 6b18aa085d chore(version): version.json [skip ci] 2025-12-02 18:05:49 +00:00
dependabot[bot]andJoe Boccanfuso 740dd7fe5f chore(deps): bump node-forge from 1.3.1 to 1.3.2 in /platform/docs (#5592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2025-12-02 13:00:17 -05:00
ohif-bot 5b75a6a56a chore(version): Update package versions [skip ci] 2025-12-02 14:53:29 +00:00
ohif-bot 3dba8dbc9f chore(version): version.json [skip ci] 2025-12-02 14:53:23 +00:00
Bill Wallace 4471416c30 fix: sculptor tool fixes (#5595)
Note: FOSSA check is invalid, but we don't have the ignore FOSSA version included.

* fix: sculptor tool fixes

* Add yarn

* update vtk.js version as well to match
2025-12-02 09:49:09 -05:00
ohif-bot a7229bb022 chore(version): Update package versions [skip ci] 2025-12-02 14:13:17 +00:00
ohif-bot 55ea706768 chore(version): version.json [skip ci] 2025-12-02 14:13:09 +00:00
dependabot[bot]andJoe Boccanfuso 6d6ce0ddae chore(deps): bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /platform/docs (#5602)
* Fix node-forge security alerts as well.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2025-12-02 09:08:43 -05:00
ohif-bot 0f26588c4b chore(version): Update package versions [skip ci] 2025-11-25 14:17:18 +00:00
ohif-bot 446f58eb76 chore(version): version.json [skip ci] 2025-11-25 14:17:11 +00:00
Pedro Köhler 411553ee91 feat: tooltips for tool settings (#5586) 2025-11-25 09:13:01 -05:00
ohif-bot e0d74a6674 chore(version): Update package versions [skip ci] 2025-11-24 18:14:22 +00:00
ohif-bot 7d0a3e6ce7 chore(version): version.json [skip ci] 2025-11-24 18:14:14 +00:00
Alireza 29cfb2c299 docs(docs): Correct grammar in README.md for clarity (#5584) 2025-11-24 13:09:50 -05:00
Joe Boccanfuso 94b0358f18 fix(docs): Empty commit as an attempt to fix the docs build. (#5582) 2025-11-24 09:46:21 -05:00
ohif-bot 173f42010b chore(version): Update package versions [skip ci] 2025-11-21 18:11:46 +00:00
ohif-bot c5a51607c9 chore(version): version.json [skip ci] 2025-11-21 18:11:39 +00:00
Bill Wallace 57205703bb Fix contour annotations RTSS saving (#5545)
* [WIP] Fix contour annotations RTSS saving

* fix: Export image sop image reference provider

* Fixes for download with filename

* Updates to fix metadata issues with CS3D

* Updates to save menu

* Fixing some additional re-save metadata

* Add support for saving with predecessor image sequence

* PR comment fixes

* fix: Load RTSS after save

* fix segmentation save

* fix load of mixed seg/rt studies

* Use frame module to get information on the frame shown

* Fix study browser to use instances.length when numImageFrames not present

* fix: Load of display set split from multiple non-FOR referenes

* Fix RT and SEG applying to wrong series because of using
referenced series sequence without checkout sop instances

* Fix save seg

* Update to released CS3D versions

* test: Add revokeObjectURL to test download blob

* Add hard dependency on mode-basic to fix build order issues

* fix: build

* Try to fix build segmentation

* PR comments

* PR comment update
2025-11-21 13:07:26 -05:00
ohif-bot 9c5043ca2c chore(version): Update package versions [skip ci] 2025-11-21 14:47:02 +00:00
ohif-bot 9899d3f623 chore(version): version.json [skip ci] 2025-11-21 14:46:55 +00:00
Dan Rukas 62d6bf437b fix(DicomUpload): Switch to use ui-next components and colors (#5576)
* Replaced legacy Button

* Updated Dialog width

* Restore text and spacing fixes

* Updated drop zone styling

* Migrated to ui-next colors
2025-11-21 09:42:14 -05:00
ohif-bot 4dc8debb02 chore(version): Update package versions [skip ci] 2025-11-20 14:33:45 +00:00
ohif-bot f72c47568b chore(version): version.json [skip ci] 2025-11-20 14:33:38 +00:00
Vinícius Alves de Faria Resende bdad2264ed tests(Playwright): Introduction of Page Object Models (POMs) to Playwright tests (#5557) 2025-11-20 09:29:14 -05:00
ohif-bot 1c991037ee chore(version): Update package versions [skip ci] 2025-11-20 01:08:59 +00:00
ohif-bot 44eafe16c1 chore(version): version.json [skip ci] 2025-11-20 01:08:52 +00:00
9ca02b4071 fix: Remove dead code and fix rectangle roi rehydration (#5490)
* Remove dead code and fix rectangle roi rehydration

* Revert bun.lock

* Remove hardcoded modality

* Add e2e tests

* Update screewnshots

* Adjustments to tests

* disabvle test for now

* Add test again

* Use ohif data soure

* Revert changes

* Test

* Update testdata to include point

* Improve tests

* Update screenshot

---------

Co-authored-by: Andrey Fedorov <andrey.fedorov@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2025-11-19 20:04:23 -05:00
ohif-bot 0be3b950b7 chore(version): Update package versions [skip ci] 2025-11-19 23:00:57 +00:00
ohif-bot 186e9caeaa chore(version): version.json [skip ci] 2025-11-19 23:00:48 +00:00
Joe Boccanfuso 5c17c262ea fix(SegmentationStyle): Fix inactive contour visibility and styling. (#5563)
* Opacity, fill and outline properties for active and inactive contours
are now consistent with those for label map segmentations.
The properties for inactive segmentations are applied to BOTH
inactive contours and label maps.

* Activate the selected segmentation for a type when the alpha, outline width, or fill/outline visibility is changed.
2025-11-19 17:55:53 -05:00
dependabot[bot]andJoe Boccanfuso 62797acd9a chore(deps): bump glob from 10.4.5 to 10.5.0 (#5572)
Ignore CVE-2025-64756 (aka GHSA-5j98-mcp5-4vw2) in bun audit.

Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2025-11-19 08:43:58 -05:00
ohif-bot b15c002f60 chore(version): Update package versions [skip ci] 2025-11-13 12:17:23 +00:00
ohif-bot 9b4e66c868 chore(version): version.json [skip ci] 2025-11-13 12:17:16 +00:00
Joe Boccanfuso 02f6744dab fix(segmentation): Allow for manually added label map and contour segmentations to be used as viewport data overlays. (#5562) 2025-11-13 07:12:28 -05:00
ohif-bot 5c44fe06dc chore(version): Update package versions [skip ci] 2025-11-11 13:24:22 +00:00
ohif-bot b71952ec86 chore(version): version.json [skip ci] 2025-11-11 13:24:16 +00:00
Joe Boccanfuso 834ae3c762 fix(interpolation): Auto accept interpolation when the interpolation process is completed. (#5555)
* Update Cornerstone3D packages to 4.9.1.
2025-11-11 08:19:45 -05:00
ohif-bot 79fab8d70d chore(version): Update package versions [skip ci] 2025-11-10 21:14:05 +00:00
ohif-bot ace476649f chore(version): version.json [skip ci] 2025-11-10 21:13:57 +00:00
58aa0d1eaa fix(dev): update webpack proxy config syntax for Orthanc dev mode (#5531)
* fix(dev): update webpack proxy config syntax for Orthanc dev mode

* Add datasources and rename dicomweb to orthancProxy datasource

---------

Co-authored-by: Nilesh Gautam <nilesh.gautam@radpretation.ai>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2025-11-10 16:08:50 -05:00
ohif-bot bb45cb24b5 chore(version): Update package versions [skip ci] 2025-11-06 13:51:02 +00:00
ohif-bot 2a07964002 chore(version): version.json [skip ci] 2025-11-06 13:50:55 +00:00
Devu-trenser 16233d980f feat(metadata): metadata access improvement (#5292)
* feat(metadata): metadata access improvement

* feat: Make a few fields commonly used at the instance level inherit multiframe
2025-11-06 08:46:01 -05:00
ohif-bot 9413219206 chore(version): Update package versions [skip ci] 2025-11-05 17:05:25 +00:00
ohif-bot 14d62ce180 chore(version): version.json [skip ci] 2025-11-05 17:05:18 +00:00
Joe Boccanfuso a0556b08be fix(segmentation): When synchronizing a segmentation representation, use the type of the source viewport segmentation to determine the type of the target viewport segmentation (#5546) 2025-11-05 12:00:41 -05:00
ohif-bot bee60a5482 chore(version): Update package versions [skip ci] 2025-11-05 15:39:24 +00:00
ohif-bot 5c496c7f02 chore(version): version.json [skip ci] 2025-11-05 15:39:17 +00:00
Vinícius Alves de Faria Resende 29bd87c8c0 fix(SegmentationTools): [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool (#5535) 2025-11-05 10:34:41 -05:00
ohif-bot 0c8ab01d34 chore(version): Update package versions [skip ci] 2025-11-05 15:16:28 +00:00
ohif-bot f15887c9bf chore(version): version.json [skip ci] 2025-11-05 15:16:20 +00:00
Abhijith Sb 2481208f31 Fix : Problems related to measurement operation undo (#5541)
fixed the console error while undoing a removed annotation
2025-11-05 10:11:26 -05:00
ohif-bot 74c92fa601 chore(version): Update package versions [skip ci] 2025-10-30 20:09:19 +00:00
ohif-bot beb6a73bc7 chore(version): version.json [skip ci] 2025-10-30 20:09:11 +00:00
Joe Boccanfuso 000e42e9a6 fix(security): Exact versioning and docs dependabot alerts (#5536) 2025-10-30 16:04:19 -04:00
ohif-bot 9ad7623a25 chore(version): Update package versions [skip ci] 2025-10-29 18:22:19 +00:00
ohif-bot eb564aec5e chore(version): version.json [skip ci] 2025-10-29 18:22:11 +00:00
35bfc9b0cc feature(segmentation): Add contour tools and utilities in a side panel (#5517)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2025-10-29 14:15:52 -04:00
ohif-bot ab440f086a chore(version): Update package versions [skip ci] 2025-10-28 12:57:15 +00:00
ohif-bot 1d33bcf229 chore(version): version.json [skip ci] 2025-10-28 12:57:08 +00:00
Vinícius Alves de Faria Resende 76f7d4e23a fix(3DSegmentation): [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout (#5505) 2025-10-28 08:52:12 -04:00
ohif-bot afa9131da1 chore(version): Update package versions [skip ci] 2025-10-27 19:21:49 +00:00
ohif-bot 5b5bca5e91 chore(version): version.json [skip ci] 2025-10-27 19:21:42 +00:00
Joe Boccanfuso 1f11c83006 fix(security): Addressed dependabot alert CVE-2025-59288 concerning playwright (#5527) 2025-10-27 15:16:43 -04:00
ohif-bot 1dd4f60034 chore(version): Update package versions [skip ci] 2025-10-24 20:53:28 +00:00
ohif-bot 2013c87ddb chore(version): version.json [skip ci] 2025-10-24 20:53:21 +00:00
Joe Boccanfuso 1009c60911 fix(security): For bun, enforce frozen lockfile. For yarn, strongly suggest using frozen lockfile. (#5508)
* Updated docs for security information.
2025-10-24 16:48:35 -04:00
ohif-bot b517944cb5 chore(version): Update package versions [skip ci] 2025-10-23 13:17:02 +00:00
ohif-bot 67d6622ae4 chore(version): version.json [skip ci] 2025-10-23 13:16:54 +00:00
Ibrahim a1544432db feat(jpeg2000): Add 16-bit RGB support to JPEG2000 decoder (#5519) 2025-10-23 09:11:54 -04:00
ohif-bot e0e1763472 chore(version): Update package versions [skip ci] 2025-10-22 16:18:31 +00:00
ohif-bot b844b56839 chore(version): version.json [skip ci] 2025-10-22 16:18:25 +00:00
k3motandTom Avitan 8b9445c442 fix: Fixed the tooltip text of the window button, it was mistakenly written in Chinese in the en-us json (#5513)
Fixed the tooltip text of the window button, it was mistakenly written in Chinese in the en-US json

Co-authored-by: Tom Avitan <tom.avitan@aidoc.com>
2025-10-22 12:14:00 -04:00
ohif-bot 8193311841 chore(version): Update package versions [skip ci] 2025-10-22 15:00:24 +00:00
ohif-bot c83710fa78 chore(version): version.json [skip ci] 2025-10-22 15:00:18 +00:00
Joe Boccanfuso e07a87dcae chore(workflow): Run playwright tests for release branch PRs. (#5515) 2025-10-22 10:55:46 -04:00
ohif-bot 2bc0647861 chore(version): Update package versions [skip ci] 2025-10-21 16:03:13 +00:00
ohif-bot 2a01dcce29 chore(version): version.json [skip ci] 2025-10-21 16:03:06 +00:00
Joe Boccanfuso 1df4f843fe fix(segmentation): Fixed the manual scrolling of the segments list and implemented automatic scrolling to the active segment. (#5510)
The height of the list container is now calculated based on a parent div instead of itself.
DataRow component now has a forward ref to allow for automatic scrolling.

Added better detection to the useDynamicMaxHeight hook for when to recalculate the max height using intersection observers.

The useEffect for scrolling a segment into view used to be excessively called to the point
where it was auto scrolling after the user manually scrolled. The auto scroll useEffect is
now dependent on the segment index changing which should only happen once per active segment change.
2025-10-21 11:58:23 -04:00
ohif-bot cf0b561a49 chore(version): Update package versions [skip ci] 2025-10-20 17:12:13 +00:00
ohif-bot f1675559ee chore(version): version.json [skip ci] 2025-10-20 17:12:07 +00:00
Ibrahim ffd9ec3273 fix(rendering): Fix palette color LUT conversion causing black images (#5509)
The palette color LUT was incorrectly handling 16 bit entries, and didn't work for both array buffers and typed arrays.
2025-10-20 13:07:39 -04:00
ohif-bot 66e6d73ad6 chore(version): Update package versions [skip ci] 2025-10-20 15:28:17 +00:00
ohif-bot 07c45d0364 chore(version): version.json [skip ci] 2025-10-20 15:28:10 +00:00
Bill Wallace 503780247c fix: Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data (#5506)
* fix: [#2404] by passing the right sop uid map

* Update PR with requried CS3D fixes

* Update to released versions - had wrong release number
2025-10-20 11:23:26 -04:00
ohif-bot 82c4a0a8d0 chore(version): Update package versions [skip ci] 2025-10-18 02:18:57 +00:00
ohif-bot 6a523ff78d chore(version): version.json [skip ci] 2025-10-18 02:18:51 +00:00
Joe Boccanfuso 170e860aa1 fix(segmentation): Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. (#5503)
* fix(segmentation): Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true.

* PR feedback.

* PR feedback.

* Added tests for disableEditing and utilities for simulating drag operations.

* Update bun.lock file after merging master.
2025-10-17 22:14:31 -04:00
ohif-bot 3e1d50c8d2 chore(version): Update package versions [skip ci] 2025-10-17 12:19:47 +00:00
ohif-bot 05b80305dc chore(version): version.json [skip ci] 2025-10-17 12:19:40 +00:00
Joe Boccanfuso 4620cc3acf fix(ErrorBoundary): Allow for details to be shown in production. (#5504)
Added showErrorDetails to config to allow for details to be shown in various runtime environments.
2025-10-17 08:15:13 -04:00
322 changed files with 16392 additions and 4007 deletions

No files matched your search

+7 -3
View File
@@ -119,7 +119,8 @@ jobs:
echo "🔍 Running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
if bun audit --audit-level high; then
# Ignore CVE-2025-64756 (aka GHSA-5j98-mcp5-4vw2) - glob is not used on the command line
if bun audit --ignore=GHSA-5j98-mcp5-4vw2 --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
else
@@ -135,7 +136,10 @@ jobs:
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit --audit-level low || true
# Ignore CVE-2025-64756 (aka GHSA-5j98-mcp5-4vw2) - glob is not used on the command line
bun audit --ignore=GHSA-5j98-mcp5-4vw2 --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
@@ -371,7 +375,7 @@ jobs:
name: Export Display Variable
command: export DISPLAY=:99
- cypress/install:
install-command: yarn install --no-save
install-command: yarn install --frozen-lockfile --no-save
- cypress/run-tests:
cypress-command: |
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
+3 -1
View File
@@ -1,7 +1,7 @@
name: Playwright Tests
on:
pull_request:
branches: [master]
branches: [master, release/*]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -23,6 +23,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm install -g yarn@1.22.22
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Install Playwright browsers
+1 -1
View File
@@ -19,7 +19,7 @@ echo 'Web application built and copied'
# Build && Move Docusaurus Output (for the docs themselves)
cd platform/docs
yarn install
yarn install --frozen-lockfile
yarn run build
cd ../..
mkdir -p ./.netlify/www/docs
+320
View File
@@ -3,6 +3,326 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
### Bug Fixes
* skip VOI sync for empty viewports ([#5599](https://github.com/OHIF/Viewers/issues/5599)) ([1aa4431](https://github.com/OHIF/Viewers/commit/1aa443112186851855a638ab974a3060c3e28ea0))
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
### Features
* tooltips for tool settings ([#5586](https://github.com/OHIF/Viewers/issues/5586)) ([411553e](https://github.com/OHIF/Viewers/commit/411553ee916b8a63b2a934a7e1274bc449041016))
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
### Bug Fixes
* **docs:** Empty commit as an attempt to fix the docs build. ([#5582](https://github.com/OHIF/Viewers/issues/5582)) ([94b0358](https://github.com/OHIF/Viewers/commit/94b0358f18f81fa2822a208d68decfce076ef577))
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
### Bug Fixes
* **DicomUpload:** Switch to use ui-next components and colors ([#5576](https://github.com/OHIF/Viewers/issues/5576)) ([62d6bf4](https://github.com/OHIF/Viewers/commit/62d6bf437b167ac3e60a0f134869c9f96d199d0f))
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
### Bug Fixes
* Remove dead code and fix rectangle roi rehydration ([#5490](https://github.com/OHIF/Viewers/issues/5490)) ([9ca02b4](https://github.com/OHIF/Viewers/commit/9ca02b4071f77600f7dcd87930fecdcf3d1c249f))
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
### Bug Fixes
* **segmentation:** Allow for manually added label map and contour segmentations to be used as viewport data overlays. ([#5562](https://github.com/OHIF/Viewers/issues/5562)) ([02f6744](https://github.com/OHIF/Viewers/commit/02f6744dab993f16cf2516fd5f657e0743e3fa42))
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
### Bug Fixes
* **dev:** update webpack proxy config syntax for Orthanc dev mode ([#5531](https://github.com/OHIF/Viewers/issues/5531)) ([58aa0d1](https://github.com/OHIF/Viewers/commit/58aa0d1eaae2e1a8103e6096864a4e5d7baf090e))
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
### Features
* **metadata:** metadata access improvement ([#5292](https://github.com/OHIF/Viewers/issues/5292)) ([16233d9](https://github.com/OHIF/Viewers/commit/16233d980f17abfc30461b46c6b1888be10c7840))
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
### Bug Fixes
* **segmentation:** When synchronizing a segmentation representation, use the type of the source viewport segmentation to determine the type of the target viewport segmentation ([#5546](https://github.com/OHIF/Viewers/issues/5546)) ([a0556b0](https://github.com/OHIF/Viewers/commit/a0556b08be98e0bba950ad7b9b575d7b971104ba))
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
### Bug Fixes
* **security:** Exact versioning and docs dependabot alerts ([#5536](https://github.com/OHIF/Viewers/issues/5536)) ([000e42e](https://github.com/OHIF/Viewers/commit/000e42e9a6eaa4f0878cbf877f03b09a459ad18e))
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
### Bug Fixes
* **security:** Addressed dependabot alert CVE-2025-59288 concerning playwright ([#5527](https://github.com/OHIF/Viewers/issues/5527)) ([1f11c83](https://github.com/OHIF/Viewers/commit/1f11c83006bff98cb8434f0662b4de55d5b9bf78))
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
### Bug Fixes
* **security:** For bun, enforce frozen lockfile. For yarn, strongly suggest using frozen lockfile. ([#5508](https://github.com/OHIF/Viewers/issues/5508)) ([1009c60](https://github.com/OHIF/Viewers/commit/1009c6091107d2db0768622120f916208a391343))
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
### Features
* **jpeg2000:** Add 16-bit RGB support to JPEG2000 decoder ([#5519](https://github.com/OHIF/Viewers/issues/5519)) ([a154443](https://github.com/OHIF/Viewers/commit/a1544432db42bf77864bb7df2f757cae819a4b4d))
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
### Bug Fixes
* Fixed the tooltip text of the window button, it was mistakenly written in Chinese in the en-us json ([#5513](https://github.com/OHIF/Viewers/issues/5513)) ([8b9445c](https://github.com/OHIF/Viewers/commit/8b9445c442d0a40b7e4393614313192bd006461b))
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package ohif-monorepo-root
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
### Bug Fixes
* **segmentation:** Fixed the manual scrolling of the segments list and implemented automatic scrolling to the active segment. ([#5510](https://github.com/OHIF/Viewers/issues/5510)) ([1df4f84](https://github.com/OHIF/Viewers/commit/1df4f843fe3c377643938563ff9e2e4f930d8537))
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
### Bug Fixes
* **rendering:** Fix palette color LUT conversion causing black images ([#5509](https://github.com/OHIF/Viewers/issues/5509)) ([ffd9ec3](https://github.com/OHIF/Viewers/commit/ffd9ec32730580e266ffae365a10ac3e524dd6cf))
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
### Bug Fixes
* **ErrorBoundary:** Allow for details to be shown in production. ([#5504](https://github.com/OHIF/Viewers/issues/5504)) ([4620cc3](https://github.com/OHIF/Viewers/commit/4620cc3acf89f218c3feaaf4c153a3dc8b023b23))
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package ohif-monorepo-root
+10 -3
View File
@@ -53,7 +53,7 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
## About
The OHIF Viewer can retrieve
and load images from most sources and formats; render sets in 2D, 3D, and
and load images from most sources and formats, render sets in 2D, 3D, and
reconstructed representations; allows for the manipulation, annotation, and
serialization of observations; supports internationalization, OpenID Connect,
offline use, hotkeys, and many more features.
@@ -147,7 +147,14 @@ Here is a schematic representation of our development workflow:
3. Navigate to the cloned project's directory
4. Add this repo as a `remote` named `upstream`
- `git remote add upstream https://github.com/OHIF/Viewers.git`
5. `yarn install` to restore dependencies and link projects
5. `yarn install --frozen-lockfile` to restore dependencies and link projects
:::danger
In general run `yarn install` with the `--frozen-lockfile` flag to help avoid
supply chain attacks by enforcing reproducible dependencies. That is, if the
`yarn.lock` file is clean and does NOT reference compromised packages, then
no compromised packages should land on your machine by using this flag.
:::
#### To Develop
@@ -158,7 +165,7 @@ _From this repository's root directory:_
yarn config set workspaces-experimental true
# Restore dependencies
yarn install
yarn install --frozen-lockfile
```
## Commands
+251
View File
@@ -3,6 +3,257 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @externals/devDependencies
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/devDependencies",
"description": "External dev dependencies - put dev build dependencies here",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"license": "MIT",
"private": true,
"engines": {
@@ -10,7 +10,7 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@kitware/vtk.js": "32.12.0",
"@kitware/vtk.js": "34.15.1",
"clsx": "2.1.1",
"core-js": "3.45.1",
"moment": "2.30.1"
+248
View File
@@ -3,6 +3,254 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/dicom-microscopy-viewer",
"description": "External reference to dicom-microscopy-viewer",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"license": "MIT",
"dependencies": {
"dicom-microscopy-viewer": "0.48.6"
+2 -2
View File
@@ -38,8 +38,8 @@
"resolutions": {
"**/@babel/runtime": "7.28.2",
"commander": "8.3.0",
"dcmjs": "0.43.1",
"dicomweb-client": ">=0.10.4",
"dcmjs": "0.45.0",
"dicomweb-client": "0.10.4",
"nth-check": "2.1.1",
"trim-newlines": "5.0.0",
"glob-parent": "6.0.2",
+219 -234
View File
@@ -47,15 +47,15 @@
},
"optionalDependencies": {
"@percy/cypress": "3.1.6",
"@playwright/test": "1.54.1",
"@playwright/test": "1.56.1",
},
},
"addOns/externals/devDependencies": {
"name": "@externals/devDependencies",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@kitware/vtk.js": "32.12.0",
"@kitware/vtk.js": "34.15.1",
"clsx": "2.1.1",
"core-js": "3.45.1",
"moment": "2.30.1",
@@ -137,24 +137,24 @@
},
"addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"dicom-microscopy-viewer": "0.48.6",
},
},
"extensions/cornerstone": {
"name": "@ohif/extension-cornerstone",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/ai": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/labelmap-interpolation": "4.5.5",
"@cornerstonejs/polymorphic-segmentation": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/ai": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/labelmap-interpolation": "4.12.6",
"@cornerstonejs/polymorphic-segmentation": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"@kitware/vtk.js": "34.15.1",
"html2canvas": "1.4.1",
"lodash.compact": "3.0.1",
"lodash.debounce": "4.0.8",
@@ -167,12 +167,12 @@
"peerDependencies": {
"@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.2.4",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.5.5",
"@ohif/core": "3.12.0-beta.58",
"@ohif/ui": "3.12.0-beta.58",
"dcmjs": "0.43.1",
"@cornerstonejs/dicom-image-loader": "4.12.6",
"@ohif/core": "3.12.0-beta.94",
"@ohif/ui": "3.12.0-beta.94",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -183,19 +183,19 @@
},
"extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@kitware/vtk.js": "32.12.0",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -206,16 +206,16 @@
},
"extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"react-color": "2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -226,19 +226,19 @@
},
"extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@kitware/vtk.js": "32.12.0",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -249,20 +249,20 @@
},
"extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.58",
"@ohif/extension-cornerstone": "3.12.0-beta.58",
"@ohif/extension-measurement-tracking": "3.12.0-beta.58",
"@ohif/ui": "3.12.0-beta.58",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.94",
"@ohif/extension-cornerstone": "3.12.0-beta.94",
"@ohif/extension-measurement-tracking": "3.12.0-beta.94",
"@ohif/ui": "3.12.0-beta.94",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -271,20 +271,20 @@
},
"extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -293,7 +293,7 @@
},
"extensions/default": {
"name": "@ohif/extension-default",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/calculate-suv": "1.1.0",
@@ -301,9 +301,9 @@
"lodash.uniqby": "4.7.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.58",
"@ohif/i18n": "3.12.0-beta.58",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.94",
"@ohif/i18n": "3.12.0-beta.94",
"dcmjs": "0.45.0",
"dicomweb-client": "0.10.4",
"prop-types": "15.8.1",
"react": "18.3.1",
@@ -316,21 +316,21 @@
},
"extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.2.4",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"colormap": "2.3.2",
"lodash.debounce": "4.0.8",
"mathjs": "12.4.3",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -341,15 +341,15 @@
},
"extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -358,15 +358,15 @@
},
"extensions/dicom-video": {
"name": "@ohif/extension-dicom-video",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -375,22 +375,22 @@
},
"extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@ohif/ui": "3.12.0-beta.58",
"@ohif/ui": "3.12.0-beta.94",
"@xstate/react": "3.2.2",
"xstate": "4.38.3",
},
"peerDependencies": {
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@ohif/core": "3.12.0-beta.58",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.58",
"@ohif/extension-default": "3.12.0-beta.58",
"@ohif/ui": "3.12.0-beta.58",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"@ohif/core": "3.12.0-beta.94",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.94",
"@ohif/extension-default": "3.12.0-beta.94",
"@ohif/ui": "3.12.0-beta.94",
"classnames": "2.5.1",
"dcmjs": "0.43.1",
"dcmjs": "0.45.0",
"lodash.debounce": "4.0.8",
"prop-types": "15.8.1",
"react": "18.3.1",
@@ -401,15 +401,15 @@
},
"extensions/test-extension": {
"name": "@ohif/extension-test",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -418,15 +418,15 @@
},
"extensions/tmtv": {
"name": "@ohif/extension-tmtv",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"classnames": "2.5.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -435,16 +435,16 @@
},
"extensions/usAnnotation": {
"name": "@ohif/extension-ultrasound-pleura-bline",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/ui-next": "3.12.0-beta.56",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"@ohif/ui-next": "3.12.0-beta.93",
},
"devDependencies": {
"@babel/core": "7.28.0",
@@ -487,7 +487,7 @@
},
"modes/basic": {
"name": "@ohif/mode-basic",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
},
@@ -496,19 +496,19 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
},
},
"modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
@@ -518,17 +518,17 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
},
},
"modes/basic-test-mode": {
"name": "@ohif/mode-test",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
@@ -538,19 +538,19 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/extension-measurement-tracking": "3.12.0-beta.56",
"@ohif/extension-test": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
"@ohif/extension-measurement-tracking": "3.12.0-beta.93",
"@ohif/extension-test": "3.12.0-beta.93",
},
},
"modes/longitudinal": {
"name": "@ohif/mode-longitudinal",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
@@ -560,32 +560,33 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/extension-measurement-tracking": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
"@ohif/extension-measurement-tracking": "3.12.0-beta.93",
"@ohif/mode-basic": "3.12.0-beta.93",
},
},
"modes/microscopy": {
"name": "@ohif/mode-microscopy",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-dicom-microscopy": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-dicom-microscopy": "3.12.0-beta.93",
},
},
"modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
},
@@ -594,17 +595,17 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dynamic-volume": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-tmtv": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dynamic-volume": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-tmtv": "3.12.0-beta.93",
},
},
"modes/segmentation": {
"name": "@ohif/mode-segmentation",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
@@ -636,19 +637,20 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
"@ohif/mode-basic": "3.12.0-beta.93",
},
},
"modes/tmtv": {
"name": "@ohif/mode-tmtv",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next": "17.3.1",
@@ -658,25 +660,25 @@
"webpack-merge": "5.10.0",
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/extension-measurement-tracking": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
"@ohif/extension-measurement-tracking": "3.12.0-beta.93",
},
},
"modes/usAnnotation": {
"name": "@ohif/mode-ultrasound-pleura-bline",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-ultrasound-pleura-bline": "3.12.0-beta.56",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-ultrasound-pleura-bline": "3.12.0-beta.93",
"i18next": "17.3.1",
},
"devDependencies": {
@@ -708,40 +710,40 @@
},
"platform/app": {
"name": "@ohif/app",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.2.4",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.5.5",
"@cornerstonejs/dicom-image-loader": "4.12.6",
"@emotion/serialize": "1.3.3",
"@ohif/core": "3.12.0-beta.56",
"@ohif/extension-cornerstone": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.56",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.56",
"@ohif/extension-default": "3.12.0-beta.56",
"@ohif/extension-dicom-microscopy": "3.12.0-beta.56",
"@ohif/extension-dicom-pdf": "3.12.0-beta.56",
"@ohif/extension-dicom-video": "3.12.0-beta.56",
"@ohif/extension-test": "3.12.0-beta.56",
"@ohif/extension-ultrasound-pleura-bline": "3.12.0-beta.56",
"@ohif/i18n": "3.12.0-beta.56",
"@ohif/mode-basic-dev-mode": "3.12.0-beta.56",
"@ohif/mode-longitudinal": "3.12.0-beta.56",
"@ohif/mode-microscopy": "3.12.0-beta.56",
"@ohif/mode-test": "3.12.0-beta.56",
"@ohif/mode-ultrasound-pleura-bline": "3.12.0-beta.56",
"@ohif/ui": "3.12.0-beta.56",
"@ohif/ui-next": "3.12.0-beta.56",
"@ohif/core": "3.12.0-beta.93",
"@ohif/extension-cornerstone": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-rt": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-seg": "3.12.0-beta.93",
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.93",
"@ohif/extension-default": "3.12.0-beta.93",
"@ohif/extension-dicom-microscopy": "3.12.0-beta.93",
"@ohif/extension-dicom-pdf": "3.12.0-beta.93",
"@ohif/extension-dicom-video": "3.12.0-beta.93",
"@ohif/extension-test": "3.12.0-beta.93",
"@ohif/extension-ultrasound-pleura-bline": "3.12.0-beta.93",
"@ohif/i18n": "3.12.0-beta.93",
"@ohif/mode-basic-dev-mode": "3.12.0-beta.93",
"@ohif/mode-longitudinal": "3.12.0-beta.93",
"@ohif/mode-microscopy": "3.12.0-beta.93",
"@ohif/mode-test": "3.12.0-beta.93",
"@ohif/mode-ultrasound-pleura-bline": "3.12.0-beta.93",
"@ohif/ui": "3.12.0-beta.93",
"@ohif/ui-next": "3.12.0-beta.93",
"@svgr/webpack": "8.1.0",
"@types/react": "18.3.23",
"classnames": "2.5.1",
"core-js": "3.45.1",
"cornerstone-math": "0.1.10",
"dcmjs": "0.43.1",
"dcmjs": "0.45.0",
"detect-gpu": "4.0.50",
"dicom-parser": "1.8.21",
"dotenv-webpack": "1.8.0",
@@ -787,7 +789,7 @@
},
"platform/cli": {
"name": "@ohif/cli",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"bin": {
"ohif-cli": "src/index.js",
},
@@ -811,10 +813,10 @@
},
"platform/core": {
"name": "@ohif/core",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"dcmjs": "0.43.1",
"dcmjs": "0.45.0",
"dicomweb-client": "0.10.4",
"gl-matrix": "3.4.3",
"immutability-helper": "3.1.1",
@@ -834,18 +836,18 @@
"peerDependencies": {
"@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.2.4",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/dicom-image-loader": "4.5.5",
"@ohif/ui": "3.12.0-beta.58",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/dicom-image-loader": "4.12.6",
"@ohif/ui": "3.12.0-beta.94",
"cornerstone-math": "0.1.9",
"dicom-parser": "1.8.21",
},
},
"platform/i18n": {
"name": "@ohif/i18n",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@babel/runtime": "7.28.2",
"i18next-locize-backend": "2.2.2",
@@ -870,7 +872,7 @@
},
"platform/ui": {
"name": "@ohif/ui",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@testing-library/react": "13.4.0",
"browser-detect": "0.2.28",
@@ -921,7 +923,7 @@
},
"platform/ui-next": {
"name": "@ohif/ui-next",
"version": "3.12.0-beta.58",
"version": "3.12.0-beta.94",
"dependencies": {
"@radix-ui/react-accordion": "1.2.11",
"@radix-ui/react-checkbox": "1.3.2",
@@ -966,15 +968,16 @@
},
"overrides": {
"@babel/runtime-corejs2": "7.26.10",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"axios": "1.12.0",
"body-parser": "1.20.3",
"commander": "8.3.0",
"core-js": "3.45.1",
"cross-env": "7.0.3",
"cross-spawn": "7.0.6",
"dcmjs": "0.43.1",
"form-data": ">=4.0.4",
"dcmjs": "0.45.0",
"glob-parent": "6.0.2",
"node-forge": "1.3.2",
"nth-check": "2.1.1",
"package-json": "8.1.1",
"path-to-regexp": "0.1.12",
@@ -1228,9 +1231,9 @@
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@4.5.5", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.43.1", "gl-matrix": "3.4.3", "ndarray": "1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5", "@cornerstonejs/tools": "4.5.5" } }, "sha512-tZC8ScqmSouWt5/0t9cUqtsSVW5ObhniyuZuF6QZvXOyWU7I4fkmtNX4RtTUi9cvuqoDA0nckejkEQhp5OjKEw=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@4.12.6", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.45.0", "gl-matrix": "3.4.3", "ndarray": "1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6", "@cornerstonejs/tools": "4.12.6" } }, "sha512-ukzrcNqmKK2iitrG5Zkn14ZawFFEL6QfrmEYZPb3rKJpOwb+CbEcGm3TLVlx+ugLavsZKXjB1mh1OSbLcn5SWw=="],
"@cornerstonejs/ai": ["@cornerstonejs/ai@4.5.5", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.43.1", "gl-matrix": "3.4.3", "lodash.clonedeep": "4.5.0", "ndarray": "1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5", "@cornerstonejs/tools": "4.5.5" } }, "sha512-31/ARIIr2EyDwyeNsI58XLYpbEikaPqYuR/7tQMFvqwX0plpsaAZx0wQ+ta0YBag9/vAtpuyjg1sGMaqS7v9Yw=="],
"@cornerstonejs/ai": ["@cornerstonejs/ai@4.12.6", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.45.0", "gl-matrix": "3.4.3", "lodash.clonedeep": "4.5.0", "ndarray": "1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6", "@cornerstonejs/tools": "4.12.6" } }, "sha512-TA49MsMmrb/HY1b+axMQId3Wy91C7Di26hsZJrMHVyEyzN1IZgUMYBcs9DeNUCAVXUSCip4txtpbS+oNfX1dOw=="],
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
@@ -1238,19 +1241,19 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": ["@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.2", "", {}, "sha512-aAUMK2958YNpOb/7G6e2/aG7hExTiFTASlMt/v90XA0pRHdWiNg5ny4S5SAju0FbIw4zcMnR0qfY+yW3VG2ivg=="],
"@cornerstonejs/codec-openjpeg": ["@cornerstonejs/codec-openjpeg@1.2.4", "", {}, "sha512-UT2su6xZZnCPSuWf2ldzKa/2+guQ7BGgfBSKqxanggwJHh48gZqIAzekmsLyJHMMK5YDK+ti+fzvVJhBS3Xi/g=="],
"@cornerstonejs/codec-openjpeg": ["@cornerstonejs/codec-openjpeg@1.3.0", "", {}, "sha512-hP8WAZ63AcaDYmHbBVTY04x424AglXsRHrI6VBdW4eTiJ76f0heWrEodT9Sb3sNwazzYuyMOIZNndJPeVSeHcw=="],
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
"@cornerstonejs/core": ["@cornerstonejs/core@4.5.5", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "4.4.2", "gl-matrix": "3.4.3", "loglevel": "1.9.2" } }, "sha512-fThmbuCEZosLUf46pNxAj0UiNYWpjKYNAj0pJoOLc6Ck3Q6lzsA4lF7Hv2LT9Ek0P9Hdfjwjjfjm4mkLevmZBg=="],
"@cornerstonejs/core": ["@cornerstonejs/core@4.12.6", "", { "dependencies": { "@kitware/vtk.js": "34.15.1", "comlink": "4.4.2", "gl-matrix": "3.4.3", "loglevel": "1.9.2" } }, "sha512-JCwv4Alvjvbmh2lDSvGRUuWa809HL1GqhGL8zE3VctIpKYMcE+f0Ggd+RGScDWCnkIhrKzCHaSZdn7Z0zWzjWQ=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@4.5.5", "", { "dependencies": { "@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-openjpeg": "1.2.4", "@cornerstonejs/codec-openjph": "2.4.7", "comlink": "4.4.2", "dicom-parser": "1.8.21", "jpeg-lossless-decoder-js": "2.1.2", "pako": "2.1.0", "uuid": "9.0.1" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5" } }, "sha512-v6nLkpS8rEFkNK8ROZkKQkiHDhOoGmVvknnKSuK0tWpN3Lpha9tfAfWiZyOJGV/HJcZhmXQcnbk3hR8/QUEs6w=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@4.12.6", "", { "dependencies": { "@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjph": "2.4.7", "comlink": "4.4.2", "dicom-parser": "1.8.21", "jpeg-lossless-decoder-js": "2.1.2", "pako": "2.1.0", "uuid": "9.0.1" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6" } }, "sha512-m94cWABepuYVaVNB5BY6YV0lkNVgwD1sDReJ1oubjk0bfZuAKQqM0AnpHLg0HLKf3TySTg3Nr1PbuI0yGw+eow=="],
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@4.5.5", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5", "@cornerstonejs/tools": "4.5.5", "@kitware/vtk.js": "32.12.1" } }, "sha512-76z3uL1J/ASS2d0qBApDVq8JQ3S5mXMD+xZhfrrbqPCkk4X9d9UyLyjylxDgFjlE3QRL+l27/nAyn4yshW6wHA=="],
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@4.12.6", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6", "@cornerstonejs/tools": "4.12.6", "@kitware/vtk.js": "34.15.1" } }, "sha512-DeR+0B0ebZejNQ3P1uRYlijHH5cf6HJpZFVM8ZyDkTkgnusx7BCRHwjRnx/Tigp83KuaOz0SJNBJ+THQRfyXRw=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@4.5.5", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5", "@cornerstonejs/tools": "4.5.5", "@kitware/vtk.js": "32.12.1" } }, "sha512-KO6nM/NovzYz3txLKlpAciJDUpIQHI9cga1PpvnqtxGMxdLkXOkaKHeeN506qQD9MAGkTqpy2Z6pYflgRG9PYw=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@4.12.6", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6", "@cornerstonejs/tools": "4.12.6", "@kitware/vtk.js": "34.15.1" } }, "sha512-gP8Ww7D9n+LKdylEPWQ4H4j73Wlnck8Gbj2KAPAjoo6EUSRlStQvs8+M8HHdscAF5RPBpyTqL/Jrz+TNlChY3A=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@4.5.5", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "4.4.2", "lodash.get": "4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "4.5.5", "@kitware/vtk.js": "32.12.1", "@types/d3-array": "3.2.1", "@types/d3-interpolate": "3.0.4", "d3-array": "3.2.4", "d3-interpolate": "3.0.1", "gl-matrix": "3.4.3" } }, "sha512-IruCR6KWYM4Z1opnPod8bNkEtJnnQyfFXr0ilmRNDNHj6jQ1/uWQQX/Ss+eWnkAALCUYAU1Co/6zWL5Nuowp8A=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@4.12.6", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "4.4.2", "lodash.get": "4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "4.12.6", "@kitware/vtk.js": "34.15.1", "@types/d3-array": "3.2.1", "@types/d3-interpolate": "3.0.4", "d3-array": "3.2.4", "d3-interpolate": "3.0.1", "gl-matrix": "3.4.3" } }, "sha512-E9FWdsNrliYgaU7AS6ErEBVt/s6bL1DRLfGB7dAtrHBbcdH3d6fDoiHeNjCh/H2U7iw6Odqm8pQnGLHxW6ZV1w=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
@@ -1420,7 +1423,7 @@
"@jsonjoy.com/util": ["@jsonjoy.com/util@1.8.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-HeR0JQNEdBozt+FrfyM5T0X3R+fIN0D+BRDkxPP5o41fTWzHfeZEqtK16aTW8haU+h+SG7XYq9PP5kobvOmkSA=="],
"@kitware/vtk.js": ["@kitware/vtk.js@32.12.0", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "xml2json": "Utilities/XMLConverter/xml2json-cli.js", "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js" } }, "sha512-QKO2BCAtNsl0p62RoA7vJbQ2NAJPyf0upPddOoNC+Qpfa1gPTGDatdyPm2/hLF9mYov6Lom7dEpfSVv8/bekzA=="],
"@kitware/vtk.js": ["@kitware/vtk.js@34.15.1", "", { "dependencies": { "@babel/runtime": "^7.28.2", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-9BH/a++drhclknBKNVzVpcOzDK95nc/OflKpQE8nY+IlaXTB1+Z6WXS6IwkEEUkRRUqSVskcW4ksgWdxihB49w=="],
"@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="],
@@ -1610,7 +1613,7 @@
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
"@playwright/test": ["@playwright/test@1.54.1", "", { "dependencies": { "playwright": "1.54.1" }, "bin": { "playwright": "cli.js" } }, "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw=="],
"@playwright/test": ["@playwright/test@1.56.1", "", { "dependencies": { "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" } }, "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg=="],
"@pmmmwh/react-refresh-webpack-plugin": ["@pmmmwh/react-refresh-webpack-plugin@0.5.17", "", { "dependencies": { "ansi-html": "^0.0.9", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", "html-entities": "^2.1.0", "loader-utils": "^2.0.4", "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "peerDependencies": { "@types/webpack": "4.x || 5.x", "react-refresh": ">=0.10.0 <1.0.0", "sockjs-client": "^1.4.0", "type-fest": ">=0.17.0 <5.0.0", "webpack": ">=4.43.0 <6.0.0", "webpack-dev-server": "3.x || 4.x || 5.x", "webpack-hot-middleware": "2.x", "webpack-plugin-serve": "0.x || 1.x" }, "optionalPeers": ["@types/webpack", "sockjs-client", "type-fest", "webpack-dev-server", "webpack-hot-middleware", "webpack-plugin-serve"] }, "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ=="],
@@ -2678,7 +2681,7 @@
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
"dcmjs": ["dcmjs@0.43.1", "", { "dependencies": { "@babel/runtime-corejs3": "^7.22.5", "adm-zip": "^0.5.10", "gl-matrix": "^3.1.0", "lodash.clonedeep": "^4.5.0", "loglevel": "^1.8.1", "ndarray": "^1.0.19", "pako": "^2.0.4" } }, "sha512-7IAB2cs2F8QYINbfhA7PV+IDraqBuHn8N31xJc6HsyNxZXyGUPQRujcq732ACqjLhl7oJi1z8PJJgLdblQtD3Q=="],
"dcmjs": ["dcmjs@0.45.0", "", { "dependencies": { "@babel/runtime-corejs3": "^7.22.5", "adm-zip": "^0.5.10", "gl-matrix": "^3.1.0", "lodash.clonedeep": "^4.5.0", "loglevel": "^1.8.1", "ndarray": "^1.0.19", "pako": "^2.0.4" } }, "sha512-MFtACBLQB6vj/3lzHEf6iGBLFC8f/QysoDO+DVUJeLrKYQi4mXYzzBbHmZCpMXqIpj8qXXLvnP/u2ugd9DVu7Q=="],
"debounce": ["debounce@1.2.1", "", {}, "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="],
@@ -3952,7 +3955,7 @@
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"node-forge": ["node-forge@1.3.1", "", {}, "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="],
"node-forge": ["node-forge@1.3.2", "", {}, "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw=="],
"node-gyp": ["node-gyp@9.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.0.3", "nopt": "^6.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ=="],
@@ -4170,9 +4173,9 @@
"platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
"playwright": ["playwright@1.54.1", "", { "dependencies": { "playwright-core": "1.54.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g=="],
"playwright": ["playwright@1.56.1", "", { "dependencies": { "playwright-core": "1.56.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw=="],
"playwright-core": ["playwright-core@1.54.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA=="],
"playwright-core": ["playwright-core@1.56.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="],
"playwright-test-coverage": ["playwright-test-coverage@1.2.12", "", { "peerDependencies": { "@playwright/test": "^1.14.1" } }, "sha512-WdR3shV+7IWtlB1AZcXSysUyZmyAqXM9+DT3iBSS1p4SkJoWZslkTqk04l5ZExSkuaM+5fKl0M9TyUuJWuGgLA=="],
@@ -5234,14 +5237,6 @@
"@cornerstonejs/ai/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"@cornerstonejs/core/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
"@cornerstonejs/labelmap-interpolation/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
"@cornerstonejs/polymorphic-segmentation/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
"@cornerstonejs/tools/@kitware/vtk.js": ["@kitware/vtk.js@32.12.1", "", { "dependencies": { "@babel/runtime": "7.22.11", "@types/webxr": "^0.5.5", "commander": "9.2.0", "d3-scale": "4.0.2", "fast-deep-equal": "^3.1.3", "fflate": "0.7.3", "gl-matrix": "3.4.3", "globalthis": "1.0.3", "seedrandom": "3.0.5", "shader-loader": "1.3.1", "shelljs": "0.8.5", "spark-md5": "3.0.2", "stream-browserify": "3.0.0", "utif": "3.1.0", "webworker-promise": "0.5.0", "worker-loader": "3.0.8", "xmlbuilder2": "3.0.2" }, "peerDependencies": { "@babel/preset-env": "^7.17.10", "autoprefixer": "^10.4.7", "wslink": ">=1.1.0 || ^2.0.0" }, "bin": { "vtkDataConverter": "Utilities/DataGenerator/convert-cli.js", "xml2json": "Utilities/XMLConverter/xml2json-cli.js" } }, "sha512-IF8WK7WDu84035ws3HZXz8wys8O81mmeFayYFbdBEfKjgZ4k0EGCK25YCGc6Q47Zxq1iutjs3Zs2xAgGjTgGQg=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
@@ -5304,8 +5299,6 @@
"@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
"@lerna/child-process/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@lerna/child-process/execa": ["execa@5.0.0", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ=="],
@@ -6328,14 +6321,6 @@
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@cornerstonejs/core/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
"@cornerstonejs/labelmap-interpolation/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
"@cornerstonejs/polymorphic-segmentation/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
"@cornerstonejs/tools/@kitware/vtk.js/@babel/runtime": ["@babel/runtime@7.22.11", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA=="],
"@eslint/eslintrc/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
+2
View File
@@ -0,0 +1,2 @@
[install]
frozenLockfile = true
+2
View File
@@ -0,0 +1,2 @@
[install]
frozenLockfile = false
+1 -1
View File
@@ -1 +1 @@
42aa2df62622ab380f97025819bd929fa0579e14
1aa443112186851855a638ab974a3060c3e28ea0
-118
View File
@@ -1,118 +0,0 @@
diff --git a/extensions/cornerstone/src/components/ViewportOrientationMenu/ViewportOrientationMenu.tsx b/extensions/cornerstone/src/components/ViewportOrientationMenu/ViewportOrientationMenu.tsx
index 6c1e4f8f6..56dac15b5 100644
--- a/extensions/cornerstone/src/components/ViewportOrientationMenu/ViewportOrientationMenu.tsx
+++ b/extensions/cornerstone/src/components/ViewportOrientationMenu/ViewportOrientationMenu.tsx
@@ -156,65 +156,65 @@ function ViewportOrientationMenu({
>
<Button
variant="ghost"
- className="flex h-[28px] flex-shrink-0 items-center justify-start self-stretch"
+ className="flex h-7 w-full flex-shrink-0 items-center justify-start self-stretch px-1 py-0"
onClick={() => handleOrientationChange('axial')}
>
- <span className="mr-0 flex w-4 items-center justify-center">
+ <div className="mr-1 flex w-6 items-center justify-start">
{currentOrientation === 'axial' ? (
- <Icons.Checked className="text-primary h-5 w-5" />
+ <Icons.Checked className="text-primary h-6 w-6" />
) : null}
- </span>
- <span className="flex-1 text-left">Axial</span>
+ </div>
+ <div className="flex-1 text-left">Axial</div>
</Button>
<Button
variant="ghost"
- className="flex h-[28px] flex-shrink-0 items-center justify-start self-stretch"
+ className="flex h-7 w-full flex-shrink-0 items-center justify-start self-stretch px-1 py-0"
onClick={() => handleOrientationChange('sagittal')}
>
- <span className="mr-0 flex w-4 items-center justify-center">
+ <div className="mr-1 flex w-6 items-center justify-start">
{currentOrientation === 'sagittal' ? (
- <Icons.Checked className="text-primary h-5 w-5" />
+ <Icons.Checked className="text-primary h-6 w-6" />
) : null}
- </span>
- <span className="flex-1 text-left">Sagittal</span>
+ </div>
+ <div className="flex-1 text-left">Sagittal</div>
</Button>
<Button
variant="ghost"
- className="flex h-[28px] flex-shrink-0 items-center justify-start self-stretch"
+ className="flex h-7 w-full flex-shrink-0 items-center justify-start self-stretch px-1 py-0"
onClick={() => handleOrientationChange('coronal')}
>
- <span className="mr-0 flex w-4 items-center justify-center">
+ <div className="mr-1 flex w-6 items-center justify-start">
{currentOrientation === 'coronal' ? (
- <Icons.Checked className="text-primary h-5 w-5" />
+ <Icons.Checked className="text-primary h-6 w-6" />
) : null}
- </span>
- <span className="flex-1 text-left">Coronal</span>
+ </div>
+ <div className="flex-1 text-left">Coronal</div>
</Button>
<Button
variant="ghost"
- className="flex h-[28px] flex-shrink-0 items-center justify-start self-stretch"
+ className="flex h-7 w-full flex-shrink-0 items-center justify-start self-stretch px-1 py-0"
onClick={() => handleOrientationChange('acquisition')}
>
- <span className="mr-0 flex w-4 items-center justify-center">
+ <div className="mr-1 flex w-6 items-center justify-start">
{currentOrientation === 'acquisition' ? (
- <Icons.Checked className="text-primary h-5 w-5" />
+ <Icons.Checked className="text-primary h-6 w-6" />
) : null}
- </span>
- <span className="flex-1 text-left">Acquisition</span>
+ </div>
+ <div className="flex-1 text-left">Acquisition</div>
</Button>
{/* Divider */}
- <div className="my-2 border-t border-white/20" />
+ <div className="mx-1 my-2 border-t border-white/20" />
<Button
variant="ghost"
- className="flex h-[28px] flex-shrink-0 items-center justify-start self-stretch"
+ className="flex h-7 w-full flex-shrink-0 items-center justify-start self-stretch px-1 py-0"
onClick={() => handleOrientationChange('reformat')}
>
- <span className="mr-0 flex w-4 items-center justify-center">
+ <div className="mr-1 flex w-6 items-center justify-start">
{currentOrientation === 'reformat' ? (
- <Icons.Checked className="text-primary h-5 w-5" />
+ <Icons.Checked className="text-primary h-6 w-6" />
) : null}
- </span>
- <span className="flex-1 text-left">Reformat</span>
+ </div>
+ <div className="flex-1 text-left">Reformat</div>
</Button>
</PopoverContent>
</Popover>
diff --git a/platform/ui-next/src/components/Icons/Sources/Checked.tsx b/platform/ui-next/src/components/Icons/Sources/Checked.tsx
index 83c8fc57e..c88b75e0d 100644
--- a/platform/ui-next/src/components/Icons/Sources/Checked.tsx
+++ b/platform/ui-next/src/components/Icons/Sources/Checked.tsx
@@ -4,14 +4,14 @@ import type { IconProps } from '../types';
export const Checked = (props: IconProps) => (
<svg
{...props}
- width="24"
+ width="20"
height="24"
- viewBox="0 0 24 24"
+ viewBox="0 0 20 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
- d="M16 9L11.3333 15L8 12.4737"
+ d="M14 9L9.3333 15L6 12.4737"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
@@ -3,6 +3,275 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "DICOM Parametric Map read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"@ohif/core": "3.12.0-beta.97",
"@ohif/extension-cornerstone": "3.12.0-beta.97",
"@ohif/extension-default": "3.12.0-beta.97",
"@ohif/i18n": "3.12.0-beta.97",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@kitware/vtk.js": "32.12.0",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3"
}
}
@@ -3,6 +3,254 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"@ohif/core": "3.12.0-beta.97",
"@ohif/extension-cornerstone": "3.12.0-beta.97",
"@ohif/extension-default": "3.12.0-beta.97",
"@ohif/i18n": "3.12.0-beta.97",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -17,19 +17,25 @@ function _getDisplaySetsFromSeries(
servicesManager: AppTypes.ServicesManager,
extensionManager
) {
const instance = instances[0];
utils.sortStudyInstances(instances);
// Choose the LAST instance in the list as the most recently created one.
const instance = instances[instances.length - 1];
const {
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
SeriesDescription,
SeriesDescription = '',
SeriesNumber,
SeriesDate,
SeriesTime,
StructureSetDate,
StructureSetTime,
SOPClassUID,
wadoRoot,
wadoUri,
wadoUriRoot,
imageId: predecessorImageId,
} = instance;
const displaySet = {
@@ -39,7 +45,13 @@ function _getDisplaySetsFromSeries(
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
/**
* The "SeriesDate" for a display set is really the display set date, which
* should be the date of the instance being used, which will be the structure
* set date in this case.
*/
SeriesDate: StructureSetDate || SeriesDate,
SeriesTime: StructureSetTime || SeriesTime,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
@@ -54,6 +66,8 @@ function _getDisplaySetsFromSeries(
structureSet: null,
sopClassUids,
instance,
instances,
predecessorImageId,
wadoRoot,
wadoUriRoot,
wadoUri,
@@ -62,7 +76,10 @@ function _getDisplaySetsFromSeries(
};
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) {
if (
instance.ReferencedFrameOfReferenceSequence?.RTReferencedStudySequence &&
!instance.ReferencedSeriesSequence
) {
instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
instance.ReferencedFrameOfReferenceSequence
);
@@ -70,7 +87,8 @@ function _getDisplaySetsFromSeries(
}
if (!referencedSeriesSequence) {
throw new Error('ReferencedSeriesSequence is missing for the RTSTRUCT');
console.error('ReferencedSeriesSequence is missing for the RTSTRUCT');
return;
}
const referencedSeries = referencedSeriesSequence[0];
@@ -79,9 +97,13 @@ function _getDisplaySetsFromSeries(
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
const { displaySetService } = servicesManager.services;
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
displaySet.referencedSeriesInstanceUID
);
const referencedDisplaySets =
displaySetService.getDisplaySetsForReferences(referencedSeriesSequence);
if (referencedDisplaySets?.length > 1) {
console.warn(
'Reference applies to more than 1 display set for Contours, applying only to first display set'
);
}
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
// Instead of throwing error, subscribe to display sets added
@@ -97,7 +119,7 @@ function _getDisplaySetsFromSeries(
}
);
} else {
const referencedDisplaySet = referencedDisplaySets[0];
const [referencedDisplaySet] = referencedDisplaySets;
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
}
@@ -89,9 +89,12 @@ async function checkAndLoadContourData({
ROIContour.ContourSequence.forEach((Contour, index) => {
const promise = resolvedPromises[index];
if (promise.status === 'fulfilled') {
if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) {
if (
Array.isArray(promise.value) &&
promise.value.every(it => Number.isFinite(Number(it)))
) {
// If promise.value is already an array of numbers, use it directly
Contour.ContourData = promise.value;
Contour.ContourData = promise.value.map(Number);
} else {
// If the resolved promise value is a byte array (Blob), it needs to be decoded
const uint8Array = new Uint8Array(promise.value);
@@ -103,7 +103,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
evt => {
if (evt.rtDisplaySet.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
if (evt.rtDisplaySet?.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
setRtIsLoading(false);
}
@@ -3,6 +3,275 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"@ohif/core": "3.12.0-beta.97",
"@ohif/extension-cornerstone": "3.12.0-beta.97",
"@ohif/extension-default": "3.12.0-beta.97",
"@ohif/i18n": "3.12.0-beta.97",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@kitware/vtk.js": "32.12.0",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@kitware/vtk.js": "34.15.1",
"react-color": "2.19.3"
}
}
@@ -1,15 +1,13 @@
import dcmjs from 'dcmjs';
import { classes, Types } from '@ohif/core';
import { classes, Types, utils } from '@ohif/core';
import { cache, metaData } from '@cornerstonejs/core';
import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools';
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
import { createReportDialogPrompt } from '@ohif/extension-default';
import { createReportDialogPrompt, useUIStateStore } from '@ohif/extension-default';
import { DicomMetadataStore } from '@ohif/core';
import PROMPT_RESPONSES from '../../default/src/utils/_shared/PROMPT_RESPONSES';
const { datasetToBlob } = dcmjs.data;
const getTargetViewport = ({ viewportId, viewportGridService }) => {
const { viewports, activeViewportId } = viewportGridService.getState();
const targetViewportId = viewportId || activeViewportId;
@@ -27,7 +25,7 @@ const {
const {
Cornerstone3D: {
RTSS: { generateRTSSFromSegmentations },
RTSS: { generateRTSSFromRepresentation },
},
} = adaptersRT;
@@ -37,7 +35,7 @@ const commandsModule = ({
servicesManager,
extensionManager,
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
const { segmentationService, displaySetService, viewportGridService, toolGroupService } =
const { segmentationService, displaySetService, viewportGridService } =
servicesManager.services as AppTypes.Services;
const actions = {
@@ -92,6 +90,7 @@ const commandsModule = ({
*/
generateSegmentation: ({ segmentationId, options = {} }) => {
const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId);
const predecessorImageId = options.predecessorImageId ?? segmentation.predecessorImageId;
const { imageIds } = segmentation.representationData.Labelmap;
@@ -173,12 +172,10 @@ const commandsModule = ({
labelmap3D.metadata[segmentIndex] = segmentMetadata;
});
const generatedSegmentation = generateSegmentation(
referencedImages,
labelmap3D,
metaData,
options
);
const generatedSegmentation = generateSegmentation(referencedImages, labelmap3D, metaData, {
predecessorImageId,
...options,
});
return generatedSegmentation;
},
@@ -212,24 +209,28 @@ const commandsModule = ({
* @returns {Object|void} Returns the naturalized report if successfully stored,
* otherwise throws an error.
*/
storeSegmentation: async ({ segmentationId, dataSource }) => {
storeSegmentation: async ({ segmentationId, dataSource, modality = 'SEG' }) => {
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation) {
throw new Error('No segmentation found');
}
const { label } = segmentation;
const { label, predecessorImageId } = segmentation;
const defaultDataSource = dataSource ?? extensionManager.getActiveDataSource()[0];
const {
value: reportName,
dataSourceName: selectedDataSource,
series,
priorSeriesNumber,
action,
} = await createReportDialogPrompt({
servicesManager,
extensionManager,
predecessorImageId,
title: 'Store Segmentation',
modality,
});
if (action === PROMPT_RESPONSES.CREATE_REPORT) {
@@ -238,12 +239,18 @@ const commandsModule = ({
? extensionManager.getDataSources(selectedDataSource)[0]
: defaultDataSource;
const generatedData = actions.generateSegmentation({
const args = {
segmentationId,
options: {
SeriesDescription: reportName || label || 'Research Derived Series',
SeriesDescription: series ? undefined : reportName || label || 'Contour Series',
SeriesNumber: series ? undefined : 1 + priorSeriesNumber,
predecessorImageId: series,
},
});
};
const generatedDataAsync =
(modality === 'SEG' && actions.generateSegmentation(args)) ||
(modality === 'RTSTRUCT' && actions.generateContour(args));
const generatedData = await generatedDataAsync;
if (!generatedData || !generatedData.dataset) {
throw new Error('Error during segmentation generation');
@@ -270,14 +277,9 @@ const commandsModule = ({
}
}
},
/**
* Converts segmentations into RTSS for download.
* This sample function retrieves all segentations and passes to
* cornerstone tool adapter to convert to DICOM RTSS format. It then
* converts dataset to downloadable blob.
*
*/
downloadRTSS: async ({ segmentationId }) => {
generateContour: async args => {
const { segmentationId, options } = args;
const segmentations = segmentationService.getSegmentation(segmentationId);
// inject colors to the segmentIndex
@@ -287,45 +289,54 @@ const commandsModule = ({
segment.color = segmentationService.getSegmentColor(
firstRepresentation.viewportId,
segmentationId,
segmentIndex
Number(segmentIndex)
);
});
const predecessorImageId = options?.predecessorImageId ?? segmentations.predecessorImageId;
const dataset = await generateRTSSFromRepresentation(segmentations, {
predecessorImageId,
...options,
});
return { dataset };
},
const RTSS = await generateRTSSFromSegmentations(
segmentations,
classes.MetadataProvider,
DicomMetadataStore
);
/**
* Downloads an RTSS instance from a segmentation or contour
* representation.
*/
downloadRTSS: async args => {
const { dataset } = await actions.generateContour(args);
const { InstanceNumber: instanceNumber = 1, SeriesInstanceUID: seriesUID } = dataset;
try {
const reportBlob = datasetToBlob(RTSS);
//Create a URL for the binary.
const objectUrl = URL.createObjectURL(reportBlob);
window.location.assign(objectUrl);
const filename = `rtss-${seriesUID}-${instanceNumber}.dcm`;
downloadDICOMData(dataset, filename);
} catch (e) {
console.warn(e);
}
},
toggleActiveSegmentationUtility: ({ itemId: buttonId }) => {
const { uiState, setUIState } = useUIStateStore.getState();
const isButtonActive = uiState['activeSegmentationUtility'] === buttonId;
console.log('toggleActiveSegmentationUtility', isButtonActive, buttonId);
// if the button is active, clear the active segmentation utility
if (isButtonActive) {
setUIState('activeSegmentationUtility', null);
} else {
setUIState('activeSegmentationUtility', buttonId);
}
},
};
const definitions = {
loadSegmentationsForViewport: {
commandFn: actions.loadSegmentationsForViewport,
},
generateSegmentation: {
commandFn: actions.generateSegmentation,
},
downloadSegmentation: {
commandFn: actions.downloadSegmentation,
},
storeSegmentation: {
commandFn: actions.storeSegmentation,
},
downloadRTSS: {
commandFn: actions.downloadRTSS,
},
loadSegmentationsForViewport: actions.loadSegmentationsForViewport,
generateSegmentation: actions.generateSegmentation,
downloadSegmentation: actions.downloadSegmentation,
storeSegmentation: actions.storeSegmentation,
downloadRTSS: actions.downloadRTSS,
toggleActiveSegmentationUtility: actions.toggleActiveSegmentationUtility,
};
return {
@@ -0,0 +1,243 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useRunCommand, useSystem } from '@ohif/core';
import { useActiveViewportSegmentationRepresentations } from '@ohif/extension-cornerstone';
import {
Button,
cn,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Separator,
Switch,
Tabs,
TabsList,
TabsTrigger,
} from '@ohif/ui-next';
import { Icons } from '@ohif/ui-next';
import { contourSegmentation } from '@cornerstonejs/tools/utilities';
import { Segment } from '@cornerstonejs/tools/types';
const { LogicalOperation } = contourSegmentation;
const options = [
{
value: 'merge',
logicalOperation: LogicalOperation.Union,
label: 'Merge',
icon: 'actions-combine-merge',
helperIcon: 'helper-combine-merge',
},
{
value: 'intersect',
logicalOperation: LogicalOperation.Intersect,
label: 'Intersect',
icon: 'actions-combine-intersect',
helperIcon: 'helper-combine-intersect',
},
{
value: 'subtract',
logicalOperation: LogicalOperation.Subtract,
label: 'Subtract',
icon: 'actions-combine-subtract',
helperIcon: 'helper-combine-subtract',
},
];
// Shared component for segment selection
function SegmentSelector({
label,
value,
onValueChange,
segments,
placeholder = 'Select a segment',
}: {
label: string;
value: string;
onValueChange: (value: string) => void;
segments: Segment[];
placeholder?: string;
}) {
return (
<div className="flex justify-between gap-6">
<div>{label}</div>
<Select
key={`select-segment-${label}`}
onValueChange={onValueChange}
value={value}
>
<SelectTrigger className="overflow-hidden">
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{segments.map(segment => (
<SelectItem
key={segment.segmentIndex}
value={segment.segmentIndex.toString()}
>
{segment.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
}
function LogicalContourOperationOptions() {
const { servicesManager } = useSystem();
const { segmentationService } = servicesManager.services;
const { segmentationsWithRepresentations } = useActiveViewportSegmentationRepresentations();
const activeRepresentation = segmentationsWithRepresentations?.find(
({ representation }) => representation?.active
);
const segments = activeRepresentation
? Object.values(activeRepresentation.segmentation.segments)
: [];
// Calculate the next available segment index
const nextSegmentIndex = activeRepresentation
? segmentationService.getNextAvailableSegmentIndex(
activeRepresentation.segmentation.segmentationId
)
: 1;
const activeSegment = segments.find(segment => segment.active);
const activeSegmentIndex = activeSegment?.segmentIndex || 0;
const [operation, setOperation] = useState(options[0]);
const [segmentA, setSegmentA] = useState<string>(activeSegmentIndex?.toString() || '');
const [segmentB, setSegmentB] = useState<string>('');
const [createNewSegment, setCreateNewSegment] = useState<boolean>(false);
const [newSegmentName, setNewSegmentName] = useState<string>('');
useEffect(() => {
setSegmentA(activeSegmentIndex?.toString() || null);
}, [activeSegmentIndex]);
useEffect(() => {
setNewSegmentName(`Segment ${nextSegmentIndex}`);
}, [nextSegmentIndex]);
const runCommand = useRunCommand();
const applyLogicalContourOperation = useCallback(() => {
let resultSegmentIndex = segmentA;
if (createNewSegment) {
resultSegmentIndex = nextSegmentIndex.toString();
runCommand('addSegment', {
segmentationId: activeRepresentation.segmentation.segmentationId,
config: {
label: newSegmentName,
segmentIndex: nextSegmentIndex,
},
});
}
runCommand('applyLogicalContourOperation', {
segmentAInfo: {
segmentationId: activeRepresentation.segmentation.segmentationId,
segmentIndex: parseInt(segmentA),
},
segmentBInfo: {
segmentationId: activeRepresentation.segmentation.segmentationId,
segmentIndex: parseInt(segmentB),
},
resultSegmentInfo: {
segmentationId: activeRepresentation.segmentation.segmentationId,
segmentIndex: parseInt(resultSegmentIndex),
},
logicalOperation: operation.logicalOperation,
});
}, [
activeRepresentation?.segmentation?.segmentationId,
createNewSegment,
newSegmentName,
nextSegmentIndex,
operation.logicalOperation,
runCommand,
segmentA,
segmentB,
]);
return (
<div className="flex w-[245px] flex-col gap-4">
<div className="flex items-start justify-between">
<div className="flex w-auto flex-col items-center gap-2 text-base font-normal leading-none">
<Tabs value={operation.value}>
<TabsList className="inline-flex space-x-1">
{options.map(option => {
const { value, icon } = option;
return (
<TabsTrigger
value={value}
key={`logical-contour-operation-${value}`}
onClick={() => setOperation(option)}
>
<Icons.ByName name={icon}></Icons.ByName>
</TabsTrigger>
);
})}
</TabsList>
</Tabs>
<div>{operation.label}</div>
</div>
<div className="bg-primary-dark flex h-[62px] w-[88px] items-center justify-center rounded-lg">
<Icons.ByName name={operation.helperIcon}></Icons.ByName>
</div>
</div>
<SegmentSelector
label="A"
value={segmentA}
onValueChange={setSegmentA}
segments={segments}
/>
<SegmentSelector
label="B"
value={segmentB}
onValueChange={setSegmentB}
segments={segments}
/>
<div className="flex justify-end pl-[34px]">
<Button
className="border-primary/60 grow border"
variant="ghost"
onClick={() => {
applyLogicalContourOperation();
}}
>
{operation.label}
</Button>
</div>
<Separator className="bg-input mt-2 h-[1px]" />
<div className="flex flex-col gap-2">
<div className="flex items-center justify-start gap-2">
<Switch
id="logical-contour-operations-create-new-segment-switch"
onCheckedChange={setCreateNewSegment}
></Switch>
<Label htmlFor="logical-contour-operations-create-new-segment-switch">
Create a new segment
</Label>
</div>
<div className="pl-9">
<Input
className={cn(createNewSegment ? 'visible' : 'hidden')}
disabled={!createNewSegment}
id="logical-contour-operations-create-new-segment-input"
type="text"
placeholder="New segment name"
value={newSegmentName}
onChange={e => setNewSegmentName(e.target.value)}
/>
</div>
</div>
</div>
);
}
export default LogicalContourOperationOptions;
@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { Button, Input, Label, Separator } from '@ohif/ui-next';
import { useRunCommand } from '@ohif/core';
function SimplifyContourOptions() {
const [areaThreshold, setAreaThreshold] = useState(10);
const runCommand = useRunCommand();
return (
<div className="flex w-auto w-[252px] flex-col gap-[8px] text-base font-normal leading-none">
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
<div>Fill contour holes</div>
<Button
className="border-primary/60 border"
variant="ghost"
onClick={() => {
runCommand('removeContourHoles');
}}
>
Fill Holes
</Button>
<Separator className="bg-input mt-[20px] h-[1px]" />
</div>
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
<div>Remove Small Contours</div>
<div className="flex items-center gap-2 self-end">
<Label
htmlFor="simplify-contour-options"
className="text-muted-foreground"
>
Area Threshold
</Label>
<Input
id="simplify-contour-options"
className="w-20"
type="number"
value={areaThreshold}
onChange={e => setAreaThreshold(Number(e.target.value))}
/>
</div>
<Button
className="border-primary/60 border"
variant="ghost"
onClick={() => {
runCommand('removeSmallContours', {
areaThreshold,
});
}}
>
Remove Small Contours
</Button>
<Separator className="bg-input mt-[20px] h-[1px]" />
</div>
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
<div>Create New Segment from Holes</div>
<Button
className="border-primary/60 border"
variant="ghost"
onClick={() => {
runCommand('convertContourHoles');
}}
>
Create New Segment
</Button>
</div>
</div>
);
}
export default SimplifyContourOptions;
@@ -0,0 +1,39 @@
import React from 'react';
import { Button, Separator } from '@ohif/ui-next';
import { useRunCommand } from '@ohif/core';
function SmoothContoursOptions() {
const runCommand = useRunCommand();
return (
<div className="flex w-auto w-[245px] flex-col gap-[8px] text-base font-normal leading-none">
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
<div>Smooth all edges</div>
<Button
className="border-primary/60 border"
variant="ghost"
onClick={() => {
runCommand('smoothContours');
}}
>
Smooth Edges
</Button>
<Separator className="bg-input mt-[20px] h-[1px]" />
</div>
<div className="flex w-auto flex-col gap-[10px] text-base font-normal leading-none">
<div>Remove extra points</div>
<Button
className="border-primary/60 border"
variant="ghost"
onClick={() => {
runCommand('decimateContours');
}}
>
Remove Points
</Button>
</div>
</div>
);
}
export default SmoothContoursOptions;
@@ -16,19 +16,24 @@ function _getDisplaySetsFromSeries(
servicesManager: AppTypes.ServicesManager,
extensionManager
) {
const instance = instances[0];
utils.sortStudyInstances(instances);
// Choose the LAST instance in the list as the most recently created one.
const instance = instances[instances.length - 1];
const {
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
SeriesDescription,
SeriesDescription = '',
SeriesNumber,
SeriesDate,
StructureSetDate,
SOPClassUID,
wadoRoot,
wadoUri,
wadoUriRoot,
imageId: predecessorImageId,
} = instance;
const displaySet = {
@@ -38,7 +43,7 @@ function _getDisplaySetsFromSeries(
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
SeriesDate: SeriesDate || StructureSetDate || '',
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
@@ -53,6 +58,7 @@ function _getDisplaySetsFromSeries(
segments: {},
sopClassUids,
instance,
predecessorImageId,
instances: [instance],
wadoRoot,
wadoUriRoot,
@@ -73,10 +79,16 @@ function _getDisplaySetsFromSeries(
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
const { displaySetService } = servicesManager.services;
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
displaySet.referencedSeriesInstanceUID
const referencedDisplaySets = displaySetService.getDisplaySetsForReferences(
instance.ReferencedSeriesSequence
);
if (referencedDisplaySets?.length > 1) {
console.warn(
'Segmentation does not currently handle references to multiple series, defaulting to first series'
);
}
const referencedDisplaySet = referencedDisplaySets[0];
if (!referencedDisplaySet) {
@@ -1,6 +1,34 @@
import { utilities as cstUtils } from '@cornerstonejs/tools';
import { useUIStateStore } from '@ohif/extension-default';
import LogicalContourOperationsOptions from './components/LogicalContourOperationsOptions';
import SimplifyContourOptions from './components/SimplifyContourOptions';
import SmoothContoursOptions from './components/SmoothContoursOptions';
export function getToolbarModule({ servicesManager }: withAppTypes) {
const { segmentationService, toolbarService, toolGroupService } = servicesManager.services;
return [
{
name: 'cornerstone.SimplifyContourOptions',
defaultComponent: SimplifyContourOptions,
},
{
name: 'cornerstone.LogicalContourOperationsOptions',
defaultComponent: LogicalContourOperationsOptions,
},
{
name: 'cornerstone.SmoothContoursOptions',
defaultComponent: SmoothContoursOptions,
},
{
name: 'cornerstone.isActiveSegmentationUtility',
evaluate: ({ button }) => {
const { uiState } = useUIStateStore.getState();
return {
isActive: uiState[`activeSegmentationUtility`] === button.id,
};
},
},
{
name: 'evaluate.cornerstone.hasSegmentation',
evaluate: ({ viewportId }) => {
@@ -10,6 +38,30 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
};
},
},
{
name: 'evaluate.cornerstone.hasSegmentationOfType',
evaluate: ({ viewportId, segmentationRepresentationType }) => {
const segmentations = segmentationService.getSegmentationRepresentations(viewportId);
if (!segmentations?.length) {
return {
disabled: true,
disabledText: 'No segmentations available',
};
}
if (
!segmentations.some(segmentation =>
Boolean(segmentation.type === segmentationRepresentationType)
)
) {
return {
disabled: true,
disabledText: `No ${segmentationRepresentationType} segmentations available`,
};
}
},
},
{
name: 'evaluate.cornerstone.segmentation',
evaluate: ({ viewportId, button, toolNames, disabledText }) => {
@@ -68,5 +120,23 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
};
},
},
{
name: 'evaluate.cornerstone.segmentation.synchronizeDrawingRadius',
evaluate: ({ button, radiusOptionId }) => {
const toolGroupIds = toolGroupService.getToolGroupIds();
if (!toolGroupIds?.length) {
return;
}
for (const toolGroupId of toolGroupIds) {
const brushSize = cstUtils.segmentation.getBrushSizeForToolGroup(toolGroupId);
if (brushSize) {
const option = toolbarService.getOptionById(button, radiusOptionId);
option.value = brushSize;
}
}
},
},
];
}
@@ -134,7 +134,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
evt => {
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
if (evt.segDisplaySet?.displaySetInstanceUID === segDisplaySet?.displaySetInstanceUID) {
setSegIsLoading(false);
}
@@ -3,6 +3,278 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
### Bug Fixes
* Remove dead code and fix rectangle roi rehydration ([#5490](https://github.com/OHIF/Viewers/issues/5490)) ([9ca02b4](https://github.com/OHIF/Viewers/commit/9ca02b4071f77600f7dcd87930fecdcf3d1c249f))
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -34,11 +34,11 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-measurement-tracking": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.97",
"@ohif/extension-cornerstone": "3.12.0-beta.97",
"@ohif/extension-measurement-tracking": "3.12.0-beta.97",
"@ohif/ui": "3.12.0-beta.97",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"classnames": "2.5.1"
}
}
@@ -1,12 +1,14 @@
import { metaData, utilities } from '@cornerstonejs/core';
import { metaData } from '@cornerstonejs/core';
import OHIF, { DicomMetadataStore } from '@ohif/core';
import OHIF, { DicomMetadataStore, utils } from '@ohif/core';
import dcmjs from 'dcmjs';
import { adaptersSR } from '@cornerstonejs/adapters';
import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState';
import hydrateStructuredReport from './utils/hydrateStructuredReport';
const { downloadBlob } = utils;
const { MeasurementReport } = adaptersSR.Cornerstone3D;
const { log } = OHIF;
@@ -42,8 +44,6 @@ const _generateReport = (measurementData, additionalFindingTypes, options: Optio
dataset.SpecificCharacterSet = 'ISO_IR 192';
}
dataset.InstanceNumber = options.InstanceNumber ?? 1;
return dataset;
};
@@ -85,8 +85,7 @@ const commandsModule = (props: withAppTypes) => {
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
//Create a URL for the binary.
const objectUrl = URL.createObjectURL(reportBlob);
window.location.assign(objectUrl);
downloadBlob(reportBlob, { filename: 'dicom-sr.dcm' });
},
/**
@@ -100,6 +100,7 @@ function _getDisplaySetsFromSeries(
SeriesTime,
ConceptNameCodeSequence,
SOPClassUID,
imageId: predecessorImageId,
} = instance;
validateSameStudyUID(instance.StudyInstanceUID, instances);
@@ -128,6 +129,7 @@ function _getDisplaySetsFromSeries(
isImagingMeasurementReport,
sopClassUids,
instance,
predecessorImageId,
addInstances,
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('SR')}`,
};
@@ -184,6 +186,10 @@ async function _load(
srDisplaySet.referencedImages = [];
srDisplaySet.measurements = [];
}
const { predecessorImageId } = srDisplaySet;
for (const measurement of srDisplaySet.measurements) {
measurement.predecessorImageId = predecessorImageId;
}
const mappings = measurementService.getSourceMappings(
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
@@ -11,16 +11,11 @@ import {
RectangleROITool,
} from '@cornerstonejs/tools';
import { Types } from '@ohif/core';
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
import addToolInstance from './utils/addToolInstance';
import toolNames from './tools/toolNames';
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
/**
* @param {object} configuration
*/
@@ -28,8 +23,6 @@ export default function init({
configuration = {},
servicesManager,
}: Types.Extensions.ExtensionParams): void {
const { measurementService } = servicesManager.services;
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
addToolInstance(toolNames.SRLength, LengthTool);
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
@@ -39,26 +32,10 @@ export default function init({
addToolInstance(toolNames.SRAngle, AngleTool);
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
const csTools3DVer1MeasurementSource = measurementService.getSource(
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
);
const { POINT } = measurementService.VALUE_TYPES;
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'SRSCOORD3DPoint',
POINT,
SRSCOOR3DProbeMapper.toAnnotation,
SRSCOOR3DProbeMapper.toMeasurement.bind(null, { servicesManager })
);
// Modify annotation tools to use dashed lines on SR
const dashedLine = {
lineDash: '4,4',
@@ -1,203 +0,0 @@
import { type Types } from '@cornerstonejs/core';
import {
annotation,
drawing,
utilities,
Types as cs3DToolsTypes,
AnnotationDisplayTool,
} from '@cornerstonejs/tools';
import toolNames from './toolNames';
import { Annotation } from '@cornerstonejs/tools/dist/types/types';
export default class SCOORD3DPointTool extends AnnotationDisplayTool {
static toolName = toolNames.SRSCOORD3DPoint;
constructor(
toolProps = {},
defaultToolProps = {
configuration: {},
}
) {
super(toolProps, defaultToolProps);
}
_getTextBoxLinesFromLabels(labels) {
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
const labelLength = Math.min(labels.length, 5);
const lines = [];
return lines;
}
// This tool should not inherit from AnnotationTool and we should not need
// to add the following lines.
isPointNearTool = () => null;
getHandleNearImagePoint = () => null;
renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => {
const { viewport } = enabledElement;
const { element } = viewport;
const annotations = annotation.state.getAnnotations(this.getToolName(), element);
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
if (!annotations?.length) {
return;
}
// Filter toolData to only render the data for the active SR.
const filteredAnnotations = annotations;
if (!viewport._actors?.size) {
return;
}
const styleSpecifier: cs3DToolsTypes.AnnotationStyle.StyleSpecifier = {
toolGroupId: this.toolGroupId,
toolName: this.getToolName(),
viewportId: enabledElement.viewport.id,
};
for (let i = 0; i < filteredAnnotations.length; i++) {
const annotation = filteredAnnotations[i];
const annotationUID = annotation.annotationUID;
const { renderableData } = annotation.data;
const { POINT: points } = renderableData;
styleSpecifier.annotationUID = annotationUID;
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
const color = this.getStyle('color', styleSpecifier, annotation);
const options = {
color,
lineDash,
lineWidth,
};
const point = points[0][0];
// check if viewport can render it
const viewable = viewport.isReferenceViewable(
{ FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID, cameraFocalPoint: point },
{ asNearbyProjection: true }
);
if (!viewable) {
continue;
}
// render the point
const arrowPointCanvas = viewport.worldToCanvas(point);
// Todo: configure this
const arrowEndCanvas = [arrowPointCanvas[0] + 20, arrowPointCanvas[1] + 20];
const canvasCoordinates = [arrowPointCanvas, arrowEndCanvas];
drawing.drawArrow(
svgDrawingHelper,
annotationUID,
'1',
canvasCoordinates[1],
canvasCoordinates[0],
{
color: options.color,
width: options.lineWidth,
}
);
this.renderTextBox(
svgDrawingHelper,
viewport,
canvasCoordinates,
annotation,
styleSpecifier,
options
);
}
};
renderTextBox(
svgDrawingHelper,
viewport,
canvasCoordinates,
annotation,
styleSpecifier,
options = {}
) {
if (!canvasCoordinates || !annotation) {
return;
}
const { annotationUID, data = {} } = annotation;
const { labels } = data;
const textLines = [];
for (const label of labels) {
// make this generic
// fix this
if (label.label === '363698007') {
textLines.push(`Finding Site: ${label.value}`);
}
}
const { color } = options;
const adaptedCanvasCoordinates = canvasCoordinates;
// adapt coordinates if there is an adapter
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
if (!annotation.data?.handles?.textBox?.worldPosition) {
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
}
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
const textBoxUID = '1';
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
const boundingBox = drawing.drawLinkedTextBox(
svgDrawingHelper,
annotationUID,
textBoxUID,
textLines,
textBoxPosition,
canvasCoordinates,
{},
{
...textBoxOptions,
color,
}
);
const { x: left, y: top, width, height } = boundingBox;
annotation.data.handles.textBox.worldBoundingBox = {
topLeft: viewport.canvasToWorld([left, top]),
topRight: viewport.canvasToWorld([left + width, top]),
bottomLeft: viewport.canvasToWorld([left, top + height]),
bottomRight: viewport.canvasToWorld([left + width, top + height]),
};
}
public getLinkedTextBoxStyle(
specifications: cs3DToolsTypes.AnnotationStyle.StyleSpecifier,
annotation?: Annotation
): Record<string, unknown> {
// Todo: this function can be used to set different styles for different toolMode
// for the textBox.
return {
visibility: this.getStyle('textBoxVisibility', specifications, annotation),
fontFamily: this.getStyle('textBoxFontFamily', specifications, annotation),
fontSize: this.getStyle('textBoxFontSize', specifications, annotation),
color: this.getStyle('textBoxColor', specifications, annotation),
shadow: this.getStyle('textBoxShadow', specifications, annotation),
background: this.getStyle('textBoxBackground', specifications, annotation),
lineWidth: this.getStyle('textBoxLinkLineWidth', specifications, annotation),
lineDash: this.getStyle('textBoxLinkLineDash', specifications, annotation),
};
}
}
@@ -9,7 +9,6 @@ const toolNames = {
SRCobbAngle: 'SRCobbAngle',
SRRectangleROI: 'SRRectangleROI',
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
SRSCOORD3DPoint: 'SRSCOORD3DPoint',
};
export default toolNames;
@@ -1,71 +0,0 @@
const SRSCOOR3DProbe = {
toAnnotation: measurement => {},
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
toMeasurement: ({ servicesManager, getValueTypeFromToolType }, csToolsEventDetail) => {
const { displaySetService } = servicesManager.services;
const { annotation } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('Probe tool: Missing metadata or data');
return null;
}
const { toolName, FrameOfReferenceUID } = metadata;
const { points } = data.handles;
const displaySets = displaySetService
.getActiveDisplaySets()
.filter(ds => ds.FrameOfReferenceUID === FrameOfReferenceUID);
const displaySet = displaySets.filter(ds => ds.isReconstructable)[0] || displaySets[0];
const { StudyInstanceUID: referenceStudyUID, SeriesInstanceUID: referenceSeriesUID } =
displaySets[0] || {};
const displayText = getDisplayText(annotation);
return {
uid: annotationUID,
points,
metadata,
referenceStudyUID,
referenceSeriesUID,
displaySetInstanceUID: displaySet?.displaySetInstanceUID,
toolName: metadata.toolName,
label: data.label,
displayText: displayText,
data: data.cachedStats,
type: getValueTypeFromToolType?.(toolName) ?? null,
};
},
};
function getDisplayText(annotation) {
const { data } = annotation;
if (!data) {
return [''];
}
const { labels } = data;
const displayText = [];
for (const label of labels) {
// make this generic
if (label.label === '33636980076') {
displayText.push(`Finding Site: ${label.value}`);
}
}
return {
primary: displayText,
secondary: [],
};
}
export default SRSCOOR3DProbe;
@@ -64,33 +64,34 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb
const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0];
const graphicTypePoints = renderableData[graphicType];
/** TODO: Read the tool name from the DICOM SR identification type in the future. */
/**
* TODO: Read the tool name from the DICOM SR identification type in the future.
*/
let frameOfReferenceUID = null;
let planeRestriction = null;
/**
* Store the view reference for use in initial navigation
*/
if (imageId) {
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
}
/**
* Store the view reference for use in initial navigation
*/
if (valueType === 'SCOORD3D') {
const adapter = MeasurementReport.getAdapterForTrackingIdentifier(
measurement.TrackingIdentifier
);
if (!adapter) {
toolName = toolNames.SRSCOORD3DPoint;
}
// get the ReferencedFrameOfReferenceUID from the measurement
frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
planeRestriction = {
FrameOfReferenceUID: frameOfReferenceUID,
point: graphicTypePoints[0][0],
};
}
// Store the view reference for use in initial navigation
/**
* Store the view reference for use in initial navigation
*/
measurement.viewReference = {
planeRestriction,
FrameOfReferenceUID: frameOfReferenceUID,
@@ -54,6 +54,11 @@ export default function hydrateStructuredReport(
const disableEditing = customizationService.getCustomization('panelMeasurement.disableEditing');
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
const {
StudyInstanceUID: studyUID,
SeriesInstanceUID: seriesUID,
instance: { SOPInstanceUID: sopUID },
} = displaySet;
// TODO -> We should define a strict versioning somewhere.
const mappings = measurementService.getSourceMappings(
@@ -67,24 +72,16 @@ export default function hydrateStructuredReport(
);
}
const instance = DicomMetadataStore.getInstance(
displaySet.StudyInstanceUID,
displaySet.SeriesInstanceUID,
displaySet.SOPInstanceUID
);
const instance = DicomMetadataStore.getInstance(studyUID, seriesUID, sopUID);
const sopInstanceUIDToImageId = {};
const imageIdsForToolState = {};
displaySet.measurements.forEach(measurement => {
const { ReferencedSOPInstanceUID, imageId, frameNumber } = measurement;
const { ReferencedSOPInstanceUID, imageId, frameNumber = 1 } = measurement;
const key = `${ReferencedSOPInstanceUID}:${frameNumber}`;
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
imageIdsForToolState[ReferencedSOPInstanceUID] = [];
}
if (!imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber]) {
imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber] = imageId;
if (!sopInstanceUIDToImageId[key]) {
sopInstanceUIDToImageId[key] = imageId;
}
});
@@ -133,10 +130,8 @@ export default function hydrateStructuredReport(
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
// files, and looking up the imageId from sopInstanceUIDToImageId results
// in the wrong value.
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
const imageId =
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
const frameNumber = toolData.annotation.data?.frameNumber || 1;
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
if (!imageIds.includes(imageId)) {
imageIds.push(imageId);
@@ -175,9 +170,7 @@ export default function hydrateStructuredReport(
// files, and looking up the imageId from sopInstanceUIDToImageId results
// in the wrong value.
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
const imageId =
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
if (!imageId) {
return getReferenceData3D(toolData, servicesManager);
@@ -208,6 +201,7 @@ export default function hydrateStructuredReport(
const annotation = {
annotationUID: toolData.annotation.annotationUID,
data: toolData.annotation.data,
predecessorImageId: toolData.predecessorImageId,
metadata: {
...referenceData,
toolName: annotationType,
@@ -3,6 +3,275 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "OHIF extension for 4D volumes data",
"author": "OHIF",
"license": "MIT",
@@ -29,12 +29,12 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/extension-cornerstone": "3.12.0-beta.66",
"@ohif/extension-default": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.97",
"@ohif/extension-cornerstone": "3.12.0-beta.97",
"@ohif/extension-default": "3.12.0-beta.97",
"@ohif/i18n": "3.12.0-beta.97",
"@ohif/ui": "3.12.0-beta.97",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"classnames": "2.5.1"
}
}
@@ -1,8 +1,10 @@
import * as importedActions from './actions';
import { utilities, Enums } from '@cornerstonejs/tools';
import { cache } from '@cornerstonejs/core';
import { utils } from '@ohif/core';
const LABELMAP = Enums.SegmentationRepresentations.Labelmap;
import * as importedActions from './actions';
const { downloadCsv } = utils;
const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
const services = servicesManager.services;
@@ -37,7 +39,7 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
});
return computedDisplaySets;
},
exportTimeReportCSV: ({ segmentations, config, options, summaryStats }) => {
exportTimeReportCSV: ({ segmentations, summaryStats }) => {
const dynamic4DDisplaySet = actions.getDynamic4DDisplaySet();
const volumeId = dynamic4DDisplaySet?.displaySetInstanceUID;
@@ -203,15 +205,7 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
// Generate filename and trigger download
const filename = `${instance.PatientID}.csv`;
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
downloadCsv(csvContent, { filename });
},
swapDynamicWithComputedDisplaySet: ({ displaySet }) => {
const computedDisplaySet = displaySet;
+287
View File
@@ -3,6 +3,293 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
### Bug Fixes
* skip VOI sync for empty viewports ([#5599](https://github.com/OHIF/Viewers/issues/5599)) ([1aa4431](https://github.com/OHIF/Viewers/commit/1aa443112186851855a638ab974a3060c3e28ea0))
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
### Bug Fixes
* sculptor tool fixes ([#5595](https://github.com/OHIF/Viewers/issues/5595)) ([4471416](https://github.com/OHIF/Viewers/commit/4471416c30bf80d9f6766660d103f28c83ba7bb3))
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
### Bug Fixes
* **DicomUpload:** Switch to use ui-next components and colors ([#5576](https://github.com/OHIF/Viewers/issues/5576)) ([62d6bf4](https://github.com/OHIF/Viewers/commit/62d6bf437b167ac3e60a0f134869c9f96d199d0f))
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
### Bug Fixes
* Remove dead code and fix rectangle roi rehydration ([#5490](https://github.com/OHIF/Viewers/issues/5490)) ([9ca02b4](https://github.com/OHIF/Viewers/commit/9ca02b4071f77600f7dcd87930fecdcf3d1c249f))
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
### Bug Fixes
* **SegmentationStyle:** Fix inactive contour visibility and styling. ([#5563](https://github.com/OHIF/Viewers/issues/5563)) ([5c17c26](https://github.com/OHIF/Viewers/commit/5c17c262eaa5c1229033cfff4b3e7709e874611f))
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
### Bug Fixes
* **segmentation:** Allow for manually added label map and contour segmentations to be used as viewport data overlays. ([#5562](https://github.com/OHIF/Viewers/issues/5562)) ([02f6744](https://github.com/OHIF/Viewers/commit/02f6744dab993f16cf2516fd5f657e0743e3fa42))
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
### Bug Fixes
* **interpolation:** Auto accept interpolation when the interpolation process is completed. ([#5555](https://github.com/OHIF/Viewers/issues/5555)) ([834ae3c](https://github.com/OHIF/Viewers/commit/834ae3c762ca309187afebdf77bad1ad9a57f03f))
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
### Bug Fixes
* **segmentation:** When synchronizing a segmentation representation, use the type of the source viewport segmentation to determine the type of the target viewport segmentation ([#5546](https://github.com/OHIF/Viewers/issues/5546)) ([a0556b0](https://github.com/OHIF/Viewers/commit/a0556b08be98e0bba950ad7b9b575d7b971104ba))
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
### Bug Fixes
* **SegmentationTools:** [Bug] Changes of brush/eraser radius with hotkey do not reflect on segmentation tool ([#5535](https://github.com/OHIF/Viewers/issues/5535)) ([29bd87c](https://github.com/OHIF/Viewers/commit/29bd87c8c0592a9a1350e687b862f5452627aece))
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
### Bug Fixes
* **3DSegmentation:** [Bug] The viewports become blank when loading the seg file in advanced layout after closing the seg file from any other advanced layout ([#5505](https://github.com/OHIF/Viewers/issues/5505)) ([76f7d4e](https://github.com/OHIF/Viewers/commit/76f7d4e23aa359a3397b10adf0bf09df3763f190))
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
### Features
* **jpeg2000:** Add 16-bit RGB support to JPEG2000 decoder ([#5519](https://github.com/OHIF/Viewers/issues/5519)) ([a154443](https://github.com/OHIF/Viewers/commit/a1544432db42bf77864bb7df2f757cae819a4b4d))
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
### Bug Fixes
* Pass the correct sop uid + frame for rehydration to prevent associating rehydrated measurements with the wrong data ([#5506](https://github.com/OHIF/Viewers/issues/5506)) ([5037802](https://github.com/OHIF/Viewers/commit/503780247ce7228c602a539fd88ea374e7d0d42f)), closes [#2404](https://github.com/OHIF/Viewers/issues/2404)
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
### Bug Fixes
* **segmentation:** Lock all rehydrated segmentation segments when panelSegmentation.disableEditing is true. ([#5503](https://github.com/OHIF/Viewers/issues/5503)) ([170e860](https://github.com/OHIF/Viewers/commit/170e860aa1b786ab6056cf31076804302a2bc462))
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -38,12 +38,12 @@
"peerDependencies": {
"@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.2.4",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.5.5",
"@ohif/core": "3.12.0-beta.66",
"@ohif/ui": "3.12.0-beta.66",
"dcmjs": "0.43.1",
"@cornerstonejs/dicom-image-loader": "4.12.6",
"@ohif/core": "3.12.0-beta.97",
"@ohif/ui": "3.12.0-beta.97",
"dcmjs": "0.45.0",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
"prop-types": "15.8.1",
@@ -53,14 +53,14 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.5.5",
"@cornerstonejs/ai": "4.5.5",
"@cornerstonejs/core": "4.5.5",
"@cornerstonejs/labelmap-interpolation": "4.5.5",
"@cornerstonejs/polymorphic-segmentation": "4.5.5",
"@cornerstonejs/tools": "4.5.5",
"@cornerstonejs/adapters": "4.12.6",
"@cornerstonejs/ai": "4.12.6",
"@cornerstonejs/core": "4.12.6",
"@cornerstonejs/labelmap-interpolation": "4.12.6",
"@cornerstonejs/polymorphic-segmentation": "4.12.6",
"@cornerstonejs/tools": "4.12.6",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"@kitware/vtk.js": "34.15.1",
"html2canvas": "1.4.1",
"lodash.compact": "3.0.1",
"lodash.debounce": "4.0.8",
+367 -91
View File
@@ -14,13 +14,18 @@ import {
utilities as cstUtils,
annotation,
Types as ToolTypes,
SplineContourSegmentationTool,
} from '@cornerstonejs/tools';
import {
SegmentInfo,
LogicalOperation,
OperatorOptions,
} from '@cornerstonejs/tools/utilities/contourSegmentation/logicalOperators';
import * as cornerstoneTools from '@cornerstonejs/tools';
import * as labelmapInterpolation from '@cornerstonejs/labelmap-interpolation';
import { ONNXSegmentationController } from '@cornerstonejs/ai';
import { Types as OhifTypes, utils } from '@ohif/core';
import i18n from '@ohif/i18n';
import {
callInputDialogAutoComplete,
createReportAsync,
@@ -33,7 +38,11 @@ import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/u
import { getViewportEnabledElement } from './utils/getViewportEnabledElement';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import toggleVOISliceSync from './utils/toggleVOISliceSync';
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
import {
usePositionPresentationStore,
useSegmentationPresentationStore,
useSelectedSegmentationsForViewportStore,
} from './stores';
import { toolNames } from './initCornerstoneTools';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
@@ -43,6 +52,11 @@ import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
import { getCenterExtent } from './utils/getCenterExtent';
import { EasingFunctionEnum } from './utils/transitions';
import { createSegmentationForViewport } from './utils/createSegmentationForViewport';
import { utilities as segmentationUtilities } from '@cornerstonejs/tools/segmentation';
import i18n from '@ohif/i18n';
const { add, intersect, subtract, copy } = cstUtils.contourSegmentation;
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const toggleSyncFunctions = {
@@ -106,6 +120,7 @@ function commandsModule({
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
const {
viewportGridService,
toolbarService,
toolGroupService,
cineService,
uiDialogService,
@@ -145,6 +160,25 @@ function commandsModule({
};
}
function _handleBrushSizeAction(action: 'increase' | 'decrease') {
const toolGroupIds = toolGroupService.getToolGroupIds();
if (!toolGroupIds?.length) {
return;
}
for (const toolGroupId of toolGroupIds) {
const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId);
const newBrushSize = action === 'increase' ? brushSize + 3 : brushSize - 3;
if (brushSize) {
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, newBrushSize);
toolbarService.refreshToolbarState({ toolGroupId });
}
}
}
const actions = {
jumpToMeasurementViewport: ({ annotationUID, measurement }) => {
cornerstoneTools.annotation.selection.setAnnotationSelected(annotationUID, true);
@@ -236,16 +270,28 @@ function commandsModule({
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
if (displaySet.isOverlayDisplaySet) {
// update the previously stored segmentationPresentation with the new viewportId
// presentation so that when we put the referencedDisplaySet back in the viewport
// it will have the correct segmentation representation hydrated
const segmentationType =
// Todo: check if PMAP modality should be handled such as SEG
displaySet.Modality !== 'SEG'
? SegmentationRepresentations.Contour
: viewport.type === CoreEnums.ViewportType.VOLUME_3D
? SegmentationRepresentations.Surface
: SegmentationRepresentations.Labelmap;
commandsManager.runCommand('updateStoredSegmentationPresentation', {
displaySet,
type:
displaySet.Modality === 'SEG'
? SegmentationRepresentations.Labelmap
: SegmentationRepresentations.Contour,
type: segmentationType,
});
}
@@ -266,10 +312,35 @@ function commandsModule({
referencedDisplaySetInstanceUID
);
storePositionPresentation(referencedDisplaySet);
return commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
const results = commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
viewportId,
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
});
const disableEditing = customizationService.getCustomization(
'panelSegmentation.disableEditing'
);
if (disableEditing) {
const segmentationRepresentations = segmentationService.getSegmentationRepresentations(
viewportId,
{
segmentationId: displaySet.displaySetInstanceUID,
}
);
segmentationRepresentations.forEach(representation => {
const segmentIndices = Object.keys(representation.segments);
segmentIndices.forEach(segmentIndex => {
segmentationService.setSegmentLocked(
representation.segmentationId,
parseInt(segmentIndex),
true
);
});
});
}
return results;
} else if (displaySet.Modality === 'SR') {
const results = commandsManager.runCommand('hydrateStructuredReport', {
displaySetInstanceUID: displaySet.displaySetInstanceUID,
@@ -926,17 +997,21 @@ function commandsModule({
}
}
},
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [] }) => {
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [], bindings }) => {
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
toolName = toolName || itemId || value;
toolGroupIds = toolGroupIds.length ? toolGroupIds : toolGroupService.getToolGroupIds();
toolGroupIds.forEach(toolGroupId => {
actions.setToolActive({ toolName, toolGroupId });
actions.setToolActive({ toolName, toolGroupId, bindings });
});
},
setToolActive: ({ toolName, toolGroupId = null }) => {
setToolActive: ({
toolName,
toolGroupId = null,
bindings = [{ mouseButton: Enums.MouseBindings.Primary }],
}) => {
const { viewports } = viewportGridService.getState();
if (!viewports.size) {
@@ -964,11 +1039,7 @@ function commandsModule({
// Set the new toolName to be active
toolGroup.setToolActive(toolName, {
bindings: [
{
mouseButton: Enums.MouseBindings.Primary,
},
],
bindings,
});
},
// capture viewport
@@ -1441,48 +1512,24 @@ function commandsModule({
* as a segmentation representation to the viewport.
*/
createLabelmapForViewport: async ({ viewportId, options = {} }) => {
const { viewportGridService, displaySetService, segmentationService } =
servicesManager.services;
const { viewports } = viewportGridService.getState();
const targetViewportId = viewportId;
const viewport = viewports.get(targetViewportId);
// Todo: add support for multiple display sets
const displaySetInstanceUID =
options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
const segs = segmentationService.getSegmentations();
const label = options.label || `${i18n.t('Tools:Segmentation')} ${segs.length + 1}`;
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
// This will create the segmentation and register it as a display set
const generatedSegmentationId = await segmentationService.createLabelmapForDisplaySet(
displaySet,
{
label,
segmentationId,
segments: options.createInitialSegment
? {
1: {
label: `${i18n.t('Tools:Segment')} 1`,
active: true,
},
}
: {},
}
);
// Also add the segmentation representation to the viewport
await segmentationService.addSegmentationRepresentation(viewportId, {
segmentationId,
type: Enums.SegmentationRepresentations.Labelmap,
return createSegmentationForViewport(servicesManager, {
viewportId,
options,
segmentationType: SegmentationRepresentations.Labelmap,
});
},
/**
* Creates a contour for the active viewport
*
* The created contour will be registered as a display set and also added
* as a segmentation representation to the viewport.
*/
createContourForViewport: async ({ viewportId, options = {} }) => {
return createSegmentationForViewport(servicesManager, {
viewportId,
options,
segmentationType: SegmentationRepresentations.Contour,
});
return generatedSegmentationId;
},
/**
@@ -1501,9 +1548,9 @@ function commandsModule({
* Adds a new segment to a segmentation
* @param props.segmentationId - The ID of the segmentation to add the segment to
*/
addSegmentCommand: ({ segmentationId }) => {
addSegmentCommand: ({ segmentationId, config }) => {
const { segmentationService } = servicesManager.services;
segmentationService.addSegment(segmentationId);
segmentationService.addSegment(segmentationId, config);
},
/**
@@ -1600,15 +1647,13 @@ function commandsModule({
* Stores a segmentation and shows it in the viewport
* @param props.segmentationId - The ID of the segmentation to store
*/
storeSegmentationCommand: async ({ segmentationId }) => {
storeSegmentationCommand: async args => {
const { segmentationId } = args;
const { segmentationService, viewportGridService } = servicesManager.services;
const displaySetInstanceUIDs = await createReportAsync({
servicesManager,
getReport: () =>
commandsManager.runCommand('storeSegmentation', {
segmentationId,
}),
getReport: () => commandsManager.runCommand('storeSegmentation', args),
reportType: 'Segmentation',
});
@@ -1713,6 +1758,16 @@ function commandsModule({
segmentationService.setStyle({ type }, { renderFill: value });
},
/**
* Sets whether to render fill for inactive segmentations of a segmentation type
* @param props.type - The type of segmentation
* @param props.value - Whether to render fill for inactive segmentations
*/
setRenderFillInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { renderFillInactive: value });
},
/**
* Sets whether to render outline for a segmentation type
* @param props.type - The type of segmentation
@@ -1724,13 +1779,36 @@ function commandsModule({
},
/**
* Sets the fill alpha for inactive segmentations
* Sets whether to render outline for inactive segmentations of a segmentation type
* @param props.type - The type of segmentation
* @param props.value - Whether to render outline for inactive segmentations
*/
setRenderOutlineInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { renderOutlineInactive: value });
},
/**
* Sets the fill alpha for inactive segmentations.
* If no type is provided, the fill alpha for all types will be set.
* @param props.type - The type of segmentation
* @param props.value - The alpha value to set
*/
setFillAlphaInactiveCommand: ({ type, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
if (type) {
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
} else {
segmentationService.setStyle(
{ type: SegmentationRepresentations.Labelmap },
{ fillAlphaInactive: value }
);
segmentationService.setStyle(
{ type: SegmentationRepresentations.Contour },
{ fillAlphaInactive: value }
);
}
},
editSegmentLabel: async ({ segmentationId, segmentIndex }) => {
@@ -1998,17 +2076,11 @@ function commandsModule({
segmentAI.initViewport(viewport);
}
},
setBrushSize: ({ value, toolNames }) => {
setBrushSize: ({ value }) => {
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);
});
}
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize);
});
},
setThresholdRange: ({
@@ -2037,26 +2109,10 @@ function commandsModule({
}
},
increaseBrushSize: () => {
const toolGroupIds = toolGroupService.getToolGroupIds();
if (!toolGroupIds?.length) {
return;
}
for (const toolGroupId of toolGroupIds) {
const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId);
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize + 3);
}
_handleBrushSizeAction('increase');
},
decreaseBrushSize: () => {
const toolGroupIds = toolGroupService.getToolGroupIds();
if (!toolGroupIds?.length) {
return;
}
for (const toolGroupId of toolGroupIds) {
const brushSize = segmentationUtils.getBrushSizeForToolGroup(toolGroupId);
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize - 3);
}
_handleBrushSizeAction('decrease');
},
addNewSegment: () => {
const { segmentationService } = servicesManager.services;
@@ -2232,6 +2288,205 @@ function commandsModule({
deleting,
});
},
activateSelectedSegmentationOfType: ({ segmentationRepresentationType }) => {
const { segmentationService, viewportGridService } = servicesManager.services;
const activeViewportId = viewportGridService.getActiveViewportId();
const { selectedSegmentationsForViewport } =
useSelectedSegmentationsForViewportStore.getState();
const segmentationId = selectedSegmentationsForViewport[activeViewportId]?.get(
segmentationRepresentationType
);
if (!segmentationId) {
return;
}
segmentationService.setActiveSegmentation(activeViewportId, segmentationId);
},
setDynamicCursorSizeForSculptorTool: ({ value: isDynamicCursorSize }) => {
const viewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
const sculptorToolInstance = toolGroup.getToolInstance(toolNames.SculptorTool);
const oldConfiguration = sculptorToolInstance.configuration;
sculptorToolInstance.configuration = {
...oldConfiguration,
updateCursorSize: isDynamicCursorSize ? 'dynamic' : '',
};
},
setInterpolationToolConfiguration: ({ value: interpolateContours, toolNames }) => {
const viewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
// Set the interpolation configuration for the active tool.
const activeTool = toolGroupService.getActiveToolForViewport(viewportId);
const interpolationConfig = {
interpolation: {
enabled: interpolateContours,
},
};
toolGroup.setToolConfiguration(activeTool, interpolationConfig);
// Now set the interpolation configuration for the other tools specified.
if (toolNames) {
Object.values(toolGroup.getToolInstances()).forEach(toolInstance => {
if (toolNames?.includes(toolInstance.toolName)) {
toolGroup.setToolConfiguration(toolInstance.toolName, interpolationConfig);
}
});
}
},
setSimplifiedSplineForSplineContourSegmentationTool: ({ value: simplifiedSpline }) => {
const viewportId = viewportGridService.getActiveViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
Object.values(toolGroup.getToolInstances()).forEach(toolInstance => {
if (toolInstance instanceof SplineContourSegmentationTool) {
const oldConfiguration = toolInstance.configuration;
toolInstance.configuration = {
...oldConfiguration,
simplifiedSpline,
};
}
});
},
removeSmallContours: ({ areaThreshold: threshold }) => {
const viewportId = viewportGridService.getActiveViewportId();
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
const activeSegment = segmentationService.getActiveSegment(viewportId);
if (!activeSegmentation || !activeSegment) {
return;
}
const { removeContourIslands } = segmentationUtilities;
removeContourIslands(activeSegmentation.segmentationId, activeSegment.segmentIndex, {
threshold,
});
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
renderingEngine.render();
},
applyLogicalContourOperation: ({
segmentAInfo,
segmentBInfo,
resultSegmentInfo,
logicalOperation,
}: {
segmentAInfo: SegmentInfo;
segmentBInfo: SegmentInfo;
resultSegmentInfo: OperatorOptions;
logicalOperation: LogicalOperation;
}) => {
switch (logicalOperation) {
case LogicalOperation.Union:
add(segmentAInfo, segmentBInfo, resultSegmentInfo);
break;
case LogicalOperation.Intersect:
intersect(segmentAInfo, segmentBInfo, resultSegmentInfo);
break;
case LogicalOperation.Subtract:
subtract(segmentAInfo, segmentBInfo, resultSegmentInfo);
break;
default:
throw new Error('Unsupported logical operation');
}
},
copyContourSegment: ({
sourceSegmentInfo,
targetSegmentInfo,
}: {
sourceSegmentInfo: SegmentInfo;
targetSegmentInfo?: SegmentInfo;
}) => {
if (!targetSegmentInfo) {
targetSegmentInfo = {
segmentationId: sourceSegmentInfo.segmentationId,
segmentIndex: segmentationService.getNextAvailableSegmentIndex(
sourceSegmentInfo.segmentationId
),
};
segmentationService.addSegment(targetSegmentInfo.segmentationId, {
segmentIndex: targetSegmentInfo.segmentIndex,
});
}
copy(sourceSegmentInfo, targetSegmentInfo);
},
smoothContours: () => {
const viewportId = viewportGridService.getActiveViewportId();
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
const activeSegment = segmentationService.getActiveSegment(viewportId);
if (!activeSegmentation || !activeSegment) {
return;
}
const { smoothContours } = segmentationUtilities;
smoothContours(activeSegmentation.segmentationId, activeSegment.segmentIndex);
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
renderingEngine.render();
},
removeContourHoles: () => {
const viewportId = viewportGridService.getActiveViewportId();
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
const activeSegment = segmentationService.getActiveSegment(viewportId);
if (!activeSegmentation || !activeSegment) {
return;
}
const { removeContourHoles } = segmentationUtilities;
removeContourHoles(activeSegmentation.segmentationId, activeSegment.segmentIndex);
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
renderingEngine.render();
},
decimateContours: () => {
const viewportId = viewportGridService.getActiveViewportId();
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
const activeSegment = segmentationService.getActiveSegment(viewportId);
if (!activeSegmentation || !activeSegment) {
return;
}
const { decimateContours } = segmentationUtilities;
decimateContours(activeSegmentation.segmentationId, activeSegment.segmentIndex);
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
renderingEngine.render();
},
convertContourHoles: () => {
const viewportId = viewportGridService.getActiveViewportId();
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
const activeSegment = segmentationService.getActiveSegment(viewportId);
if (!activeSegmentation || !activeSegment) {
return;
}
const targetSegmentInfo = {
segmentationId: activeSegmentation.segmentationId,
segmentIndex: segmentationService.getNextAvailableSegmentIndex(
activeSegmentation.segmentationId
),
};
segmentationService.addSegment(targetSegmentInfo.segmentationId, {
segmentIndex: targetSegmentInfo.segmentIndex,
});
const { convertContourHoles } = segmentationUtilities;
convertContourHoles(
activeSegmentation.segmentationId,
activeSegment.segmentIndex,
targetSegmentInfo.segmentationId,
targetSegmentInfo.segmentIndex
);
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
renderingEngine.render();
},
};
const definitions = {
@@ -2430,6 +2685,9 @@ function commandsModule({
createLabelmapForViewport: {
commandFn: actions.createLabelmapForViewport,
},
createContourForViewport: {
commandFn: actions.createContourForViewport,
},
setActiveSegmentation: {
commandFn: actions.setActiveSegmentation,
},
@@ -2481,9 +2739,15 @@ function commandsModule({
setRenderFill: {
commandFn: actions.setRenderFillCommand,
},
setRenderFillInactive: {
commandFn: actions.setRenderFillInactiveCommand,
},
setRenderOutline: {
commandFn: actions.setRenderOutlineCommand,
},
setRenderOutlineInactive: {
commandFn: actions.setRenderOutlineInactiveCommand,
},
setFillAlphaInactive: {
commandFn: actions.setFillAlphaInactiveCommand,
},
@@ -2531,6 +2795,18 @@ function commandsModule({
toggleSegmentLabel: actions.toggleSegmentLabel,
jumpToMeasurementViewport: actions.jumpToMeasurementViewport,
initializeSegmentLabelTool: actions.initializeSegmentLabelTool,
activateSelectedSegmentationOfType: actions.activateSelectedSegmentationOfType,
setDynamicCursorSizeForSculptorTool: actions.setDynamicCursorSizeForSculptorTool,
setSimplifiedSplineForSplineContourSegmentationTool:
actions.setSimplifiedSplineForSplineContourSegmentationTool,
removeSmallContours: actions.removeSmallContours,
applyLogicalContourOperation: actions.applyLogicalContourOperation,
copyContourSegment: actions.copyContourSegment,
smoothContours: actions.smoothContours,
removeContourHoles: actions.removeContourHoles,
decimateContours: actions.decimateContours,
convertContourHoles: actions.convertContourHoles,
setInterpolationToolConfiguration: actions.setInterpolationToolConfiguration,
};
return {
@@ -1,23 +0,0 @@
.dicom-upload-drop-area-border-dash {
background-image: repeating-linear-gradient(
to right,
#7bb2ce 0%,
#7bb2ce 50%,
transparent 50%,
transparent 100%
),
repeating-linear-gradient(to right, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%),
repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%),
repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%);
background-position:
left top,
left bottom,
left top,
right top;
background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
background-size:
20px 3px,
20px 3px,
3px 20px,
3px 20px;
}
@@ -5,8 +5,8 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import DicomFileUploader from '../../utils/DicomFileUploader';
import DicomUploadProgress from './DicomUploadProgress';
import { Button, ButtonEnums } from '@ohif/ui';
import './DicomUpload.css';
import { Button } from '@ohif/ui-next';
// Removed dashed border CSS; using simple 1px solid border with muted foreground color
type DicomUploadProps = {
dataSource;
@@ -15,7 +15,8 @@ type DicomUploadProps = {
};
function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): ReactElement {
const baseClassNames = 'min-h-[480px] flex flex-col bg-black select-none';
const baseClassNames =
'min-h-[375px] flex flex-col bg-black select-none rounded-lg overflow-hidden';
const [dicomFileUploaderArr, setDicomFileUploaderArr] = useState([]);
const onDrop = useCallback(async acceptedFiles => {
@@ -34,9 +35,10 @@ function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): R
{({ getRootProps }) => (
<div
{...getRootProps()}
className="dicom-upload-drop-area-border-dash m-5 flex h-full flex-col items-center justify-center"
className="m-5 flex h-full flex-col items-center justify-center rounded-2xl border"
style={{ borderColor: 'hsl(var(--muted-foreground) / 0.25)' }}
>
<div className="flex gap-3">
<div className="flex gap-2">
<Dropzone
onDrop={onDrop}
noDrag
@@ -44,11 +46,16 @@ function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): R
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<Button
variant="default"
size="lg"
disabled={false}
onClick={() => {}}
>
{'Add files'}
<input {...getInputProps()} />
<input
{...getInputProps()}
style={{ display: 'none' }}
/>
</Button>
</div>
)}
@@ -60,7 +67,8 @@ function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): R
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<Button
type={ButtonEnums.type.secondary}
variant="secondary"
size="lg"
disabled={false}
onClick={() => {}}
>
@@ -69,14 +77,15 @@ function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): R
{...getInputProps()}
webkitdirectory="true"
mozdirectory="true"
style={{ display: 'none' }}
/>
</Button>
</div>
)}
</Dropzone>
</div>
<div className="pt-5">or drag images or folders here</div>
<div className="text-aqua-pale pt-3 text-lg">(DICOM files supported)</div>
<div className="text-foreground pt-6 text-base">or drag images or folders here</div>
<div className="text-muted-foreground pt-1 text-base">(DICOM files supported)</div>
</div>
)}
</Dropzone>
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react';
import PropTypes from 'prop-types';
import { useSystem } from '@ohif/core';
import { Button } from '@ohif/ui';
import { Button } from '@ohif/ui-next';
import { Icons } from '@ohif/ui-next';
import DicomFileUploader, {
EVENTS,
@@ -276,7 +276,7 @@ function DicomUploadProgress({
const getNumCompletedAndTimeRemainingComponent = (): ReactElement => {
return (
<div className="bg-primary-dark flex h-14 items-center px-1 pb-4 text-lg text-white">
<div className="bg-muted flex h-14 items-center px-1 pb-4 text-lg text-white">
{numFilesCompleted === dicomFileUploaderArr.length ? (
<>
<span className={NO_WRAP_ELLIPSIS_CLASS_NAMES}>{`${dicomFileUploaderArr.length} ${
@@ -308,7 +308,7 @@ function DicomUploadProgress({
<span
className={
'text-primary hover:text-primary-lightactive:text-aqua-pale ml-auto cursor-pointer whitespace-nowrap'
'text-primary hover:text-highlight active:text-muted-foreground ml-auto cursor-pointer whitespace-nowrap'
}
onClick={cancelAllUploads}
>
@@ -337,11 +337,11 @@ function DicomUploadProgress({
const getPercentCompleteComponent = (): ReactElement => {
return (
<div className="ohif-scrollbar border-secondary-light overflow-y-scroll border-b px-2">
<div className="ohif-scrollbar border-input overflow-y-scroll border-b px-2">
<div className="min-h-14 flex w-full items-center p-2.5">
{numFilesCompleted === dicomFileUploaderArr.length ? (
<>
<div className="text-primary-light text-xl">
<div className="text-highlight text-xl">
{numFails > 0
? `Completed with ${numFails} ${numFails > 1 ? 'errors' : 'error'}!`
: 'Completed!'}
@@ -359,7 +359,7 @@ function DicomUploadProgress({
></ProgressLoadingBar>
</div>
<div className="ml-1 flex w-24 items-center">
<div className="w-10 text-right text-foreground">{`${getPercentCompleteRounded()}%`}</div>
<div className="text-foreground w-10 text-right">{`${getPercentCompleteRounded()}%`}</div>
{getShowFailedOnlyIconComponent()}
</div>
</>
@@ -56,7 +56,7 @@ const DicomUploadProgressItem = memo(
return (
<Icons.ByName
name="status-tracked"
className="text-primary-light"
className="text-highlight"
/>
);
case UploadStatus.InProgress:
@@ -71,7 +71,7 @@ const DicomUploadProgressItem = memo(
};
return (
<div className="min-h-14 border-secondary-light flex w-full items-center overflow-hidden border-b p-2.5 text-lg">
<div className="min-h-14 border-input flex w-full items-center overflow-hidden border-b p-2.5 text-lg">
<div className="self-top flex w-0 shrink grow flex-col gap-1">
<div className="flex gap-4">
<div className="flex w-6 shrink-0 items-center justify-center">{getStatusIcon()}</div>
@@ -0,0 +1,108 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuPortal,
DropdownMenuSubContent,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuSeparator,
Icons,
} from '@ohif/ui-next';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
interface ExportSegmentationSubMenuItemProps {
segmentationId: string;
segmentationRepresentationType: string;
allowExport: boolean;
actions: {
storeSegmentation: (segmentationId: string, modality?: string) => Promise<unknown>;
onSegmentationDownloadRTSS: (segmentationId: string) => void;
onSegmentationDownload: (segmentationId: string) => void;
downloadCSVSegmentationReport: (segmentationId: string) => void;
};
}
export const ExportSegmentationSubMenuItem: React.FC<ExportSegmentationSubMenuItemProps> = ({
segmentationId,
segmentationRepresentationType,
allowExport,
actions,
}) => {
const { t } = useTranslation('SegmentationTable');
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger className="pl-1">
<Icons.Export className="text-foreground" />
<span className="pl-2">{t('Download & Export')}</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuLabel className="flex items-center pl-0">
<Icons.Download className="h-5 w-5" />
<span className="pl-1">{t('Download')}</span>
</DropdownMenuLabel>
{segmentationRepresentationType === SegmentationRepresentations.Labelmap && (
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.downloadCSVSegmentationReport(segmentationId);
}}
disabled={!allowExport}
>
{t('CSV Report')}
</DropdownMenuItem>
)}
{segmentationRepresentationType === SegmentationRepresentations.Labelmap && (
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.onSegmentationDownload(segmentationId);
}}
disabled={!allowExport}
>
{t('DICOM SEG')}
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.onSegmentationDownloadRTSS(segmentationId);
}}
disabled={!allowExport}
>
{t('DICOM RTSS')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel className="flex items-center pl-0">
<Icons.Export className="h-5 w-5" />
<span className="pl-1 pt-1">{t('Export')}</span>
</DropdownMenuLabel>
{segmentationRepresentationType === SegmentationRepresentations.Labelmap && (
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.storeSegmentation(segmentationId, 'SEG');
}}
disabled={!allowExport}
>
{t('DICOM SEG')}
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.storeSegmentation(segmentationId, 'RTSTRUCT');
}}
disabled={!allowExport}
>
{t('DICOM RTSS')}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
);
};
@@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { Switch } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
export default function SegmentationToolConfig() {
const { commandsManager } = useSystem();
// Get initial states based on current configuration
const [previewEdits, setPreviewEdits] = useState(false);
const [segmentLabelEnabled, setSegmentLabelEnabled] = useState(false);
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
const [useCenterAsSegmentIndex, setUseCenterAsSegmentIndex] = useState(false);
const handlePreviewEditsChange = checked => {
setPreviewEdits(checked);
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
};
const handleToggleSegmentEnabledChange = checked => {
setToggleSegmentEnabled(checked);
commandsManager.run('toggleSegmentSelect', { toggle: checked });
};
const handleUseCenterAsSegmentIndexChange = checked => {
setUseCenterAsSegmentIndex(checked);
commandsManager.run('toggleUseCenterSegmentIndex', { toggle: checked });
};
const handleSegmentLabelEnabledChange = checked => {
setSegmentLabelEnabled(checked);
commandsManager.run('toggleSegmentLabel', { enabled: checked });
};
return (
<div className="bg-muted flex flex-col gap-2 border-b border-b-[2px] border-black px-2 py-3">
<div className="flex items-center gap-2">
<Switch
checked={previewEdits}
onCheckedChange={handlePreviewEditsChange}
/>
<span className="text-foreground text-base">Preview edits before creating</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={useCenterAsSegmentIndex}
onCheckedChange={handleUseCenterAsSegmentIndexChange}
/>
<span className="text-foreground text-base">Use center as segment index</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={toggleSegmentEnabled}
onCheckedChange={handleToggleSegmentEnabledChange}
/>
<span className="text-foreground text-base">Hover on segment border to activate</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={segmentLabelEnabled}
onCheckedChange={handleSegmentLabelEnabledChange}
/>
<span className="text-foreground text-base">Show segment name on hover</span>
</div>
</div>
);
}
@@ -0,0 +1,64 @@
import React, { useCallback } from 'react';
import { cn, ToolButton } from '@ohif/ui-next';
import { useUIStateStore } from '@ohif/extension-default';
interface SegmentationUtilityButtonProps {
className?: string;
isActive?: boolean;
id: string;
}
/**
* A button that represents a segmentation utility.
* It is implicitly the PopoverTrigger for the options Popover panel that is
* opened in the PanelSegmentation component. It in essence is the PopoverTrigger
* because typically this button has the associated options to display in the Popover.
* Furthermore it also typically has a command for toggling the activeSegmentationUtility
* value in the UIStateStore that triggers the fetching of its options.
* @param props - The props for the button.
* @param props.className - The class name for the button.
* @param props.isActive - Whether the button is active
* @param props.id - The id of the button.
*/
function SegmentationUtilityButton(props: SegmentationUtilityButtonProps) {
const { className, isActive, id } = props;
const activeSegmentationUtility = useUIStateStore(
store => store.uiState.activeSegmentationUtility
);
const toolButtonClassName = cn(
'w-7 h-7 text-primary hover:text-primary hover:!bg-primary/30',
className,
isActive && 'bg-primary/30'
);
const handleMouseDownCapture = useCallback(
event => {
if (activeSegmentationUtility === id) {
// If this active button is clicked, prevent the default Popover
// behaviour of closing the Popover on a pointer/mouse down.
// Not doing this will cause the Popover to close and then reopen again.
// Why? Because propagating this event will cause PanelSegmentation to
// close the Popover by clearing the activeSegmentationUtility. Then
// this button will set the activeSegmentationUtility again and the
// Popover will reopen.
event.preventDefault();
event.stopPropagation();
}
},
[activeSegmentationUtility, id]
);
return (
<div onPointerDownCapture={handleMouseDownCapture}>
<ToolButton
{...props}
className={toolButtonClassName}
/>
</div>
);
}
export default SegmentationUtilityButton;
@@ -3,17 +3,14 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
Icons,
useSegmentationTableContext,
useSegmentationExpanded,
} from '@ohif/ui-next';
import { useTranslation } from 'react-i18next';
import { useSystem } from '@ohif/core/src';
import { ExportSegmentationSubMenuItem } from '../components/ExportSegmentationSubMenuItem';
/**
* Custom dropdown menu component for segmentation panel that uses context for data
@@ -29,6 +26,8 @@ export const CustomDropdownMenuContent = () => {
exportOptions,
activeSegmentation,
activeSegmentationId,
segmentationRepresentationType,
disableEditing,
} = useSegmentationTableContext('CustomDropdownMenu');
// Try to get segmentation data from expanded context first, fall back to table context
@@ -47,21 +46,21 @@ export const CustomDropdownMenuContent = () => {
segmentationId = activeSegmentationId;
}
// Determine if export is allowed for this segmentation
if (exportOptions && segmentationId) {
const exportOption = exportOptions.find(opt => opt.segmentationId === segmentationId);
allowExport = exportOption?.isExportable || false;
}
if (!segmentation || !segmentationId) {
return null;
}
// Determine if export is allowed for this segmentation
if (exportOptions) {
const exportOption = exportOptions.find(opt => opt.segmentationId === segmentationId);
allowExport = exportOption?.isExportable || false;
}
const actions = {
storeSegmentation: async segmentationId => {
storeSegmentation: async (segmentationId, modality = 'SEG') => {
commandsManager.run({
commandName: 'storeSegmentation',
commandOptions: { segmentationId },
commandOptions: { segmentationId, modality },
context: 'CORNERSTONE',
});
},
@@ -78,10 +77,14 @@ export const CustomDropdownMenuContent = () => {
return (
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => onSegmentationAdd(segmentationId)}>
<Icons.Add className="text-foreground" />
<span className="pl-2">{t('Create New Segmentation')}</span>
</DropdownMenuItem>
{!disableEditing && (
<DropdownMenuItem
onClick={() => onSegmentationAdd({ segmentationId, segmentationRepresentationType })}
>
<Icons.Add className="text-foreground" />
<span className="pl-2">{t('Create New Segmentation')}</span>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuLabel>{t('Manage Current Segmentation')}</DropdownMenuLabel>
<DropdownMenuItem onClick={() => onSegmentationRemoveFromViewport(segmentationId)}>
@@ -92,61 +95,12 @@ export const CustomDropdownMenuContent = () => {
<Icons.Rename className="text-foreground" />
<span className="pl-2">{t('Rename')}</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger className="pl-1">
<Icons.Export className="text-foreground" />
<span className="pl-2">{t('Download & Export')}</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuLabel className="flex items-center pl-0">
<Icons.Download className="h-5 w-5" />
<span className="pl-1">{t('Download')}</span>
</DropdownMenuLabel>
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.downloadCSVSegmentationReport(segmentationId);
}}
disabled={!allowExport}
>
{t('CSV Report')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.onSegmentationDownload(segmentationId);
}}
disabled={!allowExport}
>
{t('DICOM SEG')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.onSegmentationDownloadRTSS(segmentationId);
}}
disabled={!allowExport}
>
{t('DICOM RTSS')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel className="flex items-center pl-0">
<Icons.Export className="h-5 w-5" />
<span className="pl-1 pt-1">{t('Export')}</span>
</DropdownMenuLabel>
<DropdownMenuItem
onClick={e => {
e.preventDefault();
actions.storeSegmentation(segmentationId);
}}
disabled={!allowExport}
>
{t('DICOM SEG')}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<ExportSegmentationSubMenuItem
segmentationId={segmentationId}
segmentationRepresentationType={segmentationRepresentationType}
allowExport={allowExport}
actions={actions}
/>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onSegmentationDelete(segmentationId)}>
<Icons.Delete className="text-red-600" />
@@ -1,6 +1,12 @@
import { CinePlayer } from '@ohif/ui-next';
import DicomUpload from '../components/DicomUpload/DicomUpload';
// Provide a wider default container for the DICOM Upload modal, without
// affecting other dialogs. We attach a static property that WorkList reads.
const DicomUploadWithSize = Object.assign(DicomUpload, {
containerClassName: 'max-w-3xl',
});
export default {
cinePlayer: CinePlayer,
autoCineModalities: ['OT', 'US'],
@@ -11,6 +17,6 @@ export default {
onBeforeDicomStore: ({ dicomDict, measurementData, naturalizedReport }) => {
return dicomDict;
},
dicomUploadComponent: DicomUpload,
dicomUploadComponent: DicomUploadWithSize,
codingValues: {},
};
@@ -1,18 +1,94 @@
import { CustomDropdownMenuContent } from './CustomDropdownMenuContent';
import { CustomSegmentStatisticsHeader } from './CustomSegmentStatisticsHeader';
import React, { useState } from 'react';
import { Switch } from '@ohif/ui-next';
import SegmentationToolConfig from '../components/SegmentationToolConfig';
import React from 'react';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
export default function getSegmentationPanelCustomization({ commandsManager, servicesManager }) {
const { segmentationService } = servicesManager.services;
let contourRenderFillChangedGlobally = false;
// Listen to when the global CONTOUR type renderFill style property is changed.
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_STYLE_MODIFIED,
({ specifier, style }) => {
if (
specifier.type === SegmentationRepresentations.Contour &&
specifier.segmentationId == null &&
specifier.viewportId == null &&
style.renderFill != null
) {
unsubscribe();
contourRenderFillChangedGlobally = true;
}
}
);
return {
'panelSegmentation.customDropdownMenuContent': CustomDropdownMenuContent,
'panelSegmentation.customSegmentStatisticsHeader': CustomSegmentStatisticsHeader,
'panelSegmentation.disableEditing': false,
'panelSegmentation.showAddSegment': true,
'panelSegmentation.onSegmentationAdd': () => {
'panelSegmentation.onSegmentationAdd': async ({
segmentationRepresentationType = SegmentationRepresentations.Labelmap,
}) => {
const { viewportGridService } = servicesManager.services;
const viewportId = viewportGridService.getState().activeViewportId;
commandsManager.run('createLabelmapForViewport', { viewportId });
if (segmentationRepresentationType === SegmentationRepresentations.Labelmap) {
commandsManager.run('createLabelmapForViewport', { viewportId });
} else if (segmentationRepresentationType === SegmentationRepresentations.Contour) {
const segmentationId = await commandsManager.run('createContourForViewport', {
viewportId,
});
// Override the default (i.e. hydrated RTSTRUCT) style for contours if the global CONTOUR type
// renderFill style property has not been changed.
if (!contourRenderFillChangedGlobally) {
segmentationService.setStyle(
{ segmentationId, type: SegmentationRepresentations.Contour },
{
renderFill: true,
renderFillInactive: true,
},
// Do not merge so that these created contours inherit other type-specific style properties like the fill alpha.
// Merging would otherwise permanently inherit the fill alpha and any inheritance from the type level would be lost.
false
);
}
// If the global CONTOUR type renderFill style property is already set, do not subscribe to the SEGMENTATION_STYLE_MODIFIED event.
if (contourRenderFillChangedGlobally) {
return;
}
// Subscribe to the SEGMENTATION_STYLE_MODIFIED event to listen for changes to the CONTOUR type renderFill style property.
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_STYLE_MODIFIED,
({ specifier, style }) => {
if (
specifier.type === SegmentationRepresentations.Contour &&
specifier.segmentationId == null &&
specifier.viewportId == null &&
style.renderFill != null
) {
// We are here because the renderFill style property is globally being changed for ALL contours.
// When this occurs, the desire is for ALL contours to inherit the property. To make this happen,
// we have to clear the style property that was set for this specific segmentation
// when it was created above.
// We can now also unsubscribe because this change only needs to be made when the global CONTOUR type
// renderFill style property is first changed.
contourRenderFillChangedGlobally = true;
unsubscribe();
segmentationService.setStyle(
{ segmentationId, type: SegmentationRepresentations.Contour },
{},
false
);
}
}
);
}
},
'panelSegmentation.tableMode': 'collapsed',
'panelSegmentation.readableText': {
@@ -33,67 +109,11 @@ export default function getSegmentationPanelCustomization({ commandsManager, ser
lesionGlycolysis: 'Lesion Glycolysis',
center: 'Center',
},
'segmentationToolbox.config': () => {
// Get initial states based on current configuration
const [previewEdits, setPreviewEdits] = useState(false);
const [segmentLabelEnabled, setSegmentLabelEnabled] = useState(false);
const [toggleSegmentEnabled, setToggleSegmentEnabled] = useState(false);
const [useCenterAsSegmentIndex, setUseCenterAsSegmentIndex] = useState(false);
const handlePreviewEditsChange = checked => {
setPreviewEdits(checked);
commandsManager.run('toggleSegmentPreviewEdit', { toggle: checked });
};
const handleToggleSegmentEnabledChange = checked => {
setToggleSegmentEnabled(checked);
commandsManager.run('toggleSegmentSelect', { toggle: checked });
};
const handleUseCenterAsSegmentIndexChange = checked => {
setUseCenterAsSegmentIndex(checked);
commandsManager.run('toggleUseCenterSegmentIndex', { toggle: checked });
};
const handleSegmentLabelEnabledChange = checked => {
setSegmentLabelEnabled(checked);
commandsManager.run('toggleSegmentLabel', { enabled: checked });
};
return (
<div className="bg-muted flex flex-col gap-2 border-b border-b-[2px] border-black px-2 py-3">
<div className="flex items-center gap-2">
<Switch
checked={previewEdits}
onCheckedChange={handlePreviewEditsChange}
/>
<span className="text-foreground text-base">Preview edits before creating</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={useCenterAsSegmentIndex}
onCheckedChange={handleUseCenterAsSegmentIndexChange}
/>
<span className="text-foreground text-base">Use center as segment index</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={toggleSegmentEnabled}
onCheckedChange={handleToggleSegmentEnabledChange}
/>
<span className="text-foreground text-base">Hover on segment border to activate</span>
</div>
<div className="flex items-center gap-2">
<Switch
checked={segmentLabelEnabled}
onCheckedChange={handleSegmentLabelEnabledChange}
/>
<span className="text-foreground text-base">Show segment name on hover</span>
</div>
</div>
);
'labelMapSegmentationToolbox.config': () => {
return <SegmentationToolConfig />;
},
'contourSegmentationToolbox.config': () => {
return <SegmentationToolConfig />;
},
};
}
+42 -13
View File
@@ -1,53 +1,67 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Toolbox } from '@ohif/extension-default';
import PanelSegmentation from './panels/PanelSegmentation';
import ActiveViewportWindowLevel from './components/ActiveViewportWindowLevel';
import PanelMeasurement from './panels/PanelMeasurement';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import i18n from '@ohif/i18n';
const getPanelModule = ({ commandsManager, servicesManager, extensionManager }: withAppTypes) => {
const wrappedPanelSegmentation = ({ configuration }) => {
const { toolbarService } = servicesManager.services;
const toolSectionMap = {
[SegmentationRepresentations.Labelmap]: toolbarService.sections.labelMapSegmentationToolbox,
[SegmentationRepresentations.Contour]: toolbarService.sections.contourSegmentationToolbox,
};
const wrappedPanelSegmentation = props => {
return (
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
/>
);
};
const wrappedPanelSegmentationNoHeader = ({ configuration }) => {
const wrappedPanelSegmentationNoHeader = props => {
return (
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
/>
);
};
const wrappedPanelSegmentationWithTools = ({ configuration }) => {
const { toolbarService } = servicesManager.services;
const wrappedPanelSegmentationWithTools = props => {
const { t } = useTranslation('SegmentationTable');
const tKey = `${props.segmentationRepresentationType ?? 'Segmentation'} tools`;
const tValue = t(tKey);
return (
<>
<Toolbox
buttonSectionId={toolbarService.sections.segmentationToolbox}
title="Segmentation Tools"
buttonSectionId={toolSectionMap[props.segmentationRepresentationType]}
title={tValue}
/>
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
...props?.configuration,
}}
segmentationRepresentationType={props?.segmentationRepresentationType}
/>
</>
);
@@ -82,11 +96,26 @@ const getPanelModule = ({ commandsManager, servicesManager, extensionManager }:
component: wrappedPanelSegmentationNoHeader,
},
{
name: 'panelSegmentationWithTools',
name: 'panelSegmentationWithToolsLabelMap',
iconName: 'tab-segmentation',
iconLabel: 'Segmentation',
label: 'Segmentation',
component: wrappedPanelSegmentationWithTools,
label: i18n.t('SegmentationTable:Labelmap'),
component: props =>
wrappedPanelSegmentationWithTools({
...props,
segmentationRepresentationType: SegmentationRepresentations.Labelmap,
}),
},
{
name: 'panelSegmentationWithToolsContour',
iconName: 'tab-contours',
iconLabel: 'Segmentation',
label: i18n.t('SegmentationTable:Contour'),
component: props =>
wrappedPanelSegmentationWithTools({
...props,
segmentationRepresentationType: SegmentationRepresentations.Contour,
}),
},
];
};
@@ -682,7 +682,7 @@ export function useViewportRendering(
const actorEntries = viewport.getActors();
const actorEntry = actorEntries?.find(entry =>
entry.referencedId.includes(activeDisplaySetInstanceUID)
entry.referencedId?.includes(activeDisplaySetInstanceUID)
);
if (!actorEntry) {
+10
View File
@@ -44,6 +44,7 @@ import {
usePositionPresentationStore,
useSegmentationPresentationStore,
useSynchronizersStore,
useSelectedSegmentationsForViewportStore,
} from './stores';
import { useToggleOneUpViewportGridStore } from '@ohif/extension-default';
import { useActiveViewportSegmentationRepresentations } from './hooks/useActiveViewportSegmentationRepresentations';
@@ -57,6 +58,7 @@ import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownload
import utils from './utils';
import { useMeasurementTracking } from './hooks/useMeasurementTracking';
import { setUpSegmentationEventHandlers } from './utils/setUpSegmentationEventHandlers';
import { setUpAnnotationEventHandlers } from './utils/setUpAnnotationEventHandlers';
export * from './components';
const { imageRetrieveMetadataProvider } = cornerstone.utilities;
@@ -102,6 +104,9 @@ const cornerstoneExtension: Types.Extensions.Extension = {
});
unsubscriptions.push(...segmentationUnsubscriptions);
const annotationUnsubscriptions = setUpAnnotationEventHandlers();
unsubscriptions.push(...annotationUnsubscriptions);
toolbarService.registerEventForToolbarUpdate(cornerstoneViewportService, [
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
]);
@@ -109,6 +114,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
toolbarService.registerEventForToolbarUpdate(segmentationService, [
segmentationService.EVENTS.SEGMENTATION_REMOVED,
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
segmentationService.EVENTS.SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
]);
toolbarService.registerEventForToolbarUpdate(cornerstone.eventTarget, [
@@ -153,6 +159,9 @@ const cornerstoneExtension: Types.Extensions.Extension = {
useSynchronizersStore.getState().clearSynchronizersStore();
useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore();
useSegmentationPresentationStore.getState().clearSegmentationPresentationStore();
useSelectedSegmentationsForViewportStore
.getState()
.clearSelectedSegmentationsForViewportStore();
segmentationService.removeAllSegmentations();
},
@@ -256,6 +265,7 @@ export {
usePositionPresentationStore,
useSegmentationPresentationStore,
useSynchronizersStore,
useSelectedSegmentationsForViewportStore,
Enums,
useMeasurements,
useActiveViewportSegmentationRepresentations,
+15 -1
View File
@@ -106,6 +106,12 @@ export default async function init({
colorbarService.EVENTS.STATE_CHANGED,
]);
toolbarService.registerEventForToolbarUpdate(segmentationService, [
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED,
segmentationService.EVENTS.SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
]);
window.services = servicesManager.services;
window.extensionManager = extensionManager;
window.commandsManager = commandsManager;
@@ -131,10 +137,18 @@ export default async function init({
getSegmentationPresentationId
);
cornerstoneTools.segmentation.config.style.setStyle(
segmentationService.setStyle(
{ type: SegmentationRepresentations.Contour },
{
// Declare these alpha values at the Contour type level so that they can be set/changed/inherited for all contour segmentations.
fillAlpha: 0.5,
fillAlphaInactive: 0.4,
// In general do not fill contours so that hydrated RTSTRUCTs are not filled in when active or inactive by default.
// However, hydrated RTSTRUCTs are filled in when active or inactive if the user chooses to fill ALL contours.
// Those Contours created in OHIF (i.e. using the Segmentation Panel) will override both fill properties upon creation.
renderFill: false,
renderFillInactive: false,
}
);
@@ -41,6 +41,10 @@ import {
SegmentSelectTool,
RegionSegmentPlusTool,
SegmentLabelTool,
LivewireContourSegmentationTool,
SculptorTool,
SplineContourSegmentationTool,
LabelMapEditWithContourTool,
} from '@cornerstonejs/tools';
import { LabelmapSlicePropagationTool, MarkerLabelmapTool } from '@cornerstonejs/ai';
import * as polySeg from '@cornerstonejs/polymorphic-segmentation';
@@ -109,6 +113,10 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(LabelmapSlicePropagationTool);
addTool(MarkerLabelmapTool);
addTool(RegionSegmentPlusTool);
addTool(LivewireContourSegmentationTool);
addTool(SculptorTool);
addTool(SplineContourSegmentationTool);
addTool(LabelMapEditWithContourTool);
// Modify annotation tools to use dashed lines on SR
const annotationStyle = {
textBoxFontSize: '15px',
@@ -168,6 +176,10 @@ const toolNames = {
LabelmapSlicePropagation: LabelmapSlicePropagationTool.toolName,
MarkerLabelmap: MarkerLabelmapTool.toolName,
RegionSegmentPlus: RegionSegmentPlusTool.toolName,
LivewireContourSegmentation: LivewireContourSegmentationTool.toolName,
SculptorTool: SculptorTool.toolName,
SplineContourSegmentation: SplineContourSegmentationTool.toolName,
LabelMapEditWithContourTool: LabelMapEditWithContourTool.toolName,
};
export { toolNames };
@@ -461,6 +461,9 @@ const connectMeasurementServiceToTools = ({
const annotationManager = annotation.state.getAnnotationManager();
const newAnnotation = {
annotationUID: measurement.uid,
// Not used in CS3D but stored in the CS3D state so that saving
// can apply the predecessor consistently.
predecessorImageId: measurement?.predecessorImageId,
highlighted: false,
isLocked: false,
// This is used to force a re-render of the annotation to
@@ -503,11 +506,15 @@ const connectMeasurementServiceToTools = ({
}
const removedAnnotation = annotation.state.getAnnotation(removedMeasurementId);
removeAnnotation(removedMeasurementId);
commandsManager.run('triggerCreateAnnotationMemo', {
annotation: removedAnnotation,
FrameOfReferenceUID: removedAnnotation.metadata.FrameOfReferenceUID,
options: { deleting: true },
});
// Ensure `removedAnnotation` is available before triggering the memo,
// as it can be undefined during an undo operation
if (removedAnnotation) {
commandsManager.run('triggerCreateAnnotationMemo', {
annotation: removedAnnotation,
FrameOfReferenceUID: removedAnnotation.metadata.FrameOfReferenceUID,
options: { deleting: true },
});
}
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
// Note: We could do a better job by triggering the render on the
// viewport itself, but the removeAnnotation does not include that info...
@@ -1,16 +1,92 @@
import React from 'react';
import { SegmentationTable } from '@ohif/ui-next';
import React, { useCallback, useEffect } from 'react';
import {
IconPresentationProvider,
Popover,
PopoverAnchor,
PopoverContent,
SegmentationTable,
ToolSettings,
} from '@ohif/ui-next';
import { useActiveViewportSegmentationRepresentations } from '../hooks/useActiveViewportSegmentationRepresentations';
import { metaData, cache } from '@cornerstonejs/core';
import { useSystem } from '@ohif/core/src';
import { useActiveToolOptions, useSystem } from '@ohif/core/src';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { Toolbar, useUIStateStore } from '@ohif/extension-default';
import SegmentationUtilityButton from '../components/SegmentationUtilityButton';
import { useSelectedSegmentationsForViewportStore } from '../stores';
import {
hasExportableLabelMapData,
hasExportableContourData,
} from '../utils/segmentationExportUtils';
export default function PanelSegmentation({ children }: withAppTypes) {
type PanelSegmentationProps = {
children?: React.ReactNode;
// The representation type for this segmentation panel. Undefined means all types.
segmentationRepresentationType?: SegmentationRepresentations;
} & withAppTypes;
export default function PanelSegmentation({
children,
segmentationRepresentationType,
}: PanelSegmentationProps) {
const { commandsManager, servicesManager } = useSystem();
const { customizationService, displaySetService } = servicesManager.services;
const {
customizationService,
displaySetService,
viewportGridService,
toolbarService,
segmentationService,
} = servicesManager.services;
const { activeViewportId } = viewportGridService.getState();
const utilitiesSectionMap = {
[SegmentationRepresentations.Labelmap]: toolbarService.sections.labelMapSegmentationUtilities,
[SegmentationRepresentations.Contour]: toolbarService.sections.contourSegmentationUtilities,
};
const selectedSegmentationsForViewportMap = useSelectedSegmentationsForViewportStore(
store => store.selectedSegmentationsForViewport[activeViewportId]
);
const selectedSegmentationIdForType = segmentationRepresentationType
? selectedSegmentationsForViewportMap?.get(segmentationRepresentationType)
: segmentationService?.getActiveSegmentation(activeViewportId)?.segmentationId;
const buttonSection = utilitiesSectionMap[segmentationRepresentationType];
const { activeToolOptions: activeUtilityOptions } = useActiveToolOptions({
buttonSectionId: buttonSection,
});
const { segmentationsWithRepresentations, disabled } =
useActiveViewportSegmentationRepresentations();
const setUIState = useUIStateStore(store => store.setUIState);
// useEffect for handling clicks on any of the non-active viewports.
// The ViewportGrid stops the propagation of pointer/mouse events
// for non-active viewports so the Popover below
// is not closed when clicking on any of the non-active viewports.
useEffect(() => {
setUIState('activeSegmentationUtility', null);
toolbarService.refreshToolbarState({ viewportId: activeViewportId });
}, [activeViewportId, setUIState, toolbarService]);
// The callback for handling clicks outside of the Popover and, the SegmentationUtilityButton
// that triggered it to open. Clicks outside those components must close the Popover.
// The Popover is made visible whenever the options associated with the
// activeSegmentationUtility exist. Thus clearing the activeSegmentationUtility
// clears the associated options and will keep the Popover closed.
const handlePopoverOpenChange = useCallback(
(open: boolean) => {
if (!open) {
setUIState('activeSegmentationUtility', null);
toolbarService.refreshToolbarState({ viewportId: activeViewportId });
}
},
[activeViewportId, setUIState, toolbarService]
);
// Extract customization options
const segmentationTableMode = customizationService.getCustomization(
'panelSegmentation.tableMode'
@@ -35,6 +111,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
},
onSegmentAdd: segmentationId => {
commandsManager.run('addSegment', { segmentationId });
commandsManager.run('setActiveSegmentation', { segmentationId });
},
onSegmentClick: (segmentationId, segmentIndex) => {
commandsManager.run('setActiveSegmentAndCenter', { segmentationId, segmentIndex });
@@ -51,6 +128,14 @@ export default function PanelSegmentation({ children }: withAppTypes) {
onSegmentDelete: (segmentationId, segmentIndex) => {
commandsManager.run('deleteSegment', { segmentationId, segmentIndex });
},
onSegmentCopy:
segmentationRepresentationType === SegmentationRepresentations.Contour
? (segmentationId, segmentIndex) => {
commandsManager.run('copyContourSegment', {
sourceSegmentInfo: { segmentationId, segmentIndex },
});
}
: undefined,
onToggleSegmentVisibility: (segmentationId, segmentIndex, type) => {
commandsManager.run('toggleSegmentVisibility', { segmentationId, segmentIndex, type });
},
@@ -76,18 +161,36 @@ export default function PanelSegmentation({ children }: withAppTypes) {
commandsManager.run('deleteSegmentation', { segmentationId });
},
setFillAlpha: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setFillAlpha', { type, value });
},
setOutlineWidth: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setOutlineWidth', { type, value });
},
setRenderFill: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setRenderFill', { type, value });
},
setRenderFillInactive: ({ type }, value) => {
commandsManager.run('setRenderFillInactive', { type, value });
},
setRenderOutline: ({ type }, value) => {
commandsManager.run('activateSelectedSegmentationOfType', {
segmentationRepresentationType: type,
});
commandsManager.run('setRenderOutline', { type, value });
},
setFillAlphaInactive: ({ type }, value) => {
setRenderOutlineInactive: ({ type }, value) => {
commandsManager.run('setRenderOutlineInactive', { type, value });
},
setFillAlphaInactive: ({ type }: { type?: string }, value) => {
commandsManager.run('setFillAlphaInactive', { type, value });
},
getRenderInactiveSegmentations: () => {
@@ -96,52 +199,26 @@ export default function PanelSegmentation({ children }: withAppTypes) {
};
// Generate export options
// Map each segmentation to an export option for it.
// A segmentation is exportable if it has any labelmap or contour data.
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
const { representationData, segmentationId } = segmentation;
const { Labelmap } = representationData;
const { Labelmap, Contour } = representationData;
if (!Labelmap) {
if (!Labelmap && !Contour) {
return { segmentationId, isExportable: true };
}
// Check if any segments have anything drawn in any of the viewports
const hasAnySegmentData = (() => {
const imageIds = Labelmap.imageIds;
if (!imageIds?.length) return false;
for (const imageId of imageIds) {
const pixelData = cache.getImage(imageId)?.getPixelData();
if (!pixelData) continue;
for (let i = 0; i < pixelData.length; i++) {
if (pixelData[i] !== 0) return true;
}
}
return false;
})();
if (!hasAnySegmentData) {
if (
!hasExportableLabelMapData(Labelmap, displaySetService) &&
!hasExportableContourData(Contour)
) {
return { segmentationId, isExportable: false };
}
const referencedImageIds = Labelmap.referencedImageIds;
const firstImageId = referencedImageIds[0];
const instance = metaData.get('instance', firstImageId);
if (!instance) {
return { segmentationId, isExportable: false };
}
const SOPInstanceUID = instance.SOPInstanceUID || instance.SopInstanceUID;
const SeriesInstanceUID = instance.SeriesInstanceUID;
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID
);
return {
segmentationId,
isExportable: displaySet?.isReconstructable,
isExportable: true,
};
});
@@ -150,15 +227,34 @@ export default function PanelSegmentation({ children }: withAppTypes) {
disabled,
data: segmentationsWithRepresentations,
mode: segmentationTableMode,
title: 'Segmentations',
title: `${segmentationRepresentationType ? `${segmentationRepresentationType} ` : ''}Segmentations`,
exportOptions,
disableEditing,
onSegmentationAdd,
showAddSegment,
renderInactiveSegmentations: handlers.getRenderInactiveSegmentations(),
segmentationRepresentationType,
selectedSegmentationIdForType,
...handlers,
};
const renderUtilitiesToolbar = () => {
if (!buttonSection) {
return null;
}
return (
<IconPresentationProvider
size="large"
IconContainer={SegmentationUtilityButton}
>
<div className="flex flex-wrap gap-[3px] bg-transparent pb-[2px] pl-[8px] pt-[6px]">
<Toolbar buttonSection={buttonSection} />
</div>
</IconPresentationProvider>
);
};
const renderSegments = () => {
return (
<SegmentationTable.Segments>
@@ -175,6 +271,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
if (tableProps.mode === 'collapsed') {
return (
<SegmentationTable.Collapsed>
{renderUtilitiesToolbar()}
<SegmentationTable.Collapsed.Header>
<SegmentationTable.Collapsed.DropdownMenu>
<CustomDropdownMenuContent />
@@ -193,6 +290,7 @@ export default function PanelSegmentation({ children }: withAppTypes) {
return (
<>
<SegmentationTable.Expanded>
{renderUtilitiesToolbar()}
<SegmentationTable.Expanded.Header>
<SegmentationTable.Expanded.DropdownMenu>
<CustomDropdownMenuContent />
@@ -211,11 +309,27 @@ export default function PanelSegmentation({ children }: withAppTypes) {
};
return (
<SegmentationTable {...tableProps}>
{children}
<SegmentationTable.Config />
<SegmentationTable.AddSegmentationRow />
{renderModeContent()}
</SegmentationTable>
<Popover
open={!!activeUtilityOptions}
onOpenChange={handlePopoverOpenChange}
>
<PopoverAnchor>
<SegmentationTable {...tableProps}>
{children}
<SegmentationTable.Config />
<SegmentationTable.AddSegmentationRow />
{renderModeContent()}
</SegmentationTable>
</PopoverAnchor>
{activeUtilityOptions && (
<PopoverContent
side="left"
align="start"
className="w-auto"
>
<ToolSettings options={activeUtilityOptions} />
</PopoverContent>
)}
</Popover>
);
}
@@ -233,7 +233,7 @@ class CornerstoneCacheService {
for (const displaySet of displaySets) {
const { Modality } = displaySet;
const isParametricMap = Modality === 'PMAP';
const isSeg = Modality === 'SEG';
const isSegOrRtstruct = Modality === 'SEG' || Modality === 'RTSTRUCT';
// Don't create volumes for the displaySets that have custom load
// function (e.g., SEG, RT, since they rely on the reference volumes
@@ -276,7 +276,7 @@ class CornerstoneCacheService {
// Parametric maps do not have image ids but they already have volume data
// therefore a new volume should not be created.
if (!isParametricMap && !isSeg && (!volumeImageIds || !volume)) {
if (!isParametricMap && !isSegOrRtstruct && (!volumeImageIds || !volume)) {
volumeImageIds = this._getCornerstoneVolumeImageIds(displaySet, dataSource);
volume = await volumeLoader.createAndCacheVolume(volumeId, {
@@ -221,7 +221,7 @@ describe('SegmentationService', () => {
it('should add event listeners', () => {
service.onModeEnter();
expect(eventTarget.addEventListener).toHaveBeenCalledTimes(7);
expect(eventTarget.addEventListener).toHaveBeenCalledTimes(8);
expect(eventTarget.addEventListener).toHaveBeenCalledWith(
csToolsEnums.Events.SEGMENTATION_MODIFIED,
@@ -251,6 +251,10 @@ describe('SegmentationService', () => {
csToolsEnums.Events.SEGMENTATION_ADDED,
expect.any(Function)
);
expect(eventTarget.addEventListener).toHaveBeenCalledWith(
csToolsEnums.Events.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
expect.any(Function)
);
});
});
@@ -1950,7 +1954,8 @@ describe('SegmentationService', () => {
type,
segmentIndex,
},
style
style,
true
);
});
});
@@ -1999,7 +2004,7 @@ describe('SegmentationService', () => {
service.addSegment(segmentationId, config);
expect(cstSegmentation.state.getSegmentation).toHaveBeenCalledTimes(1);
expect(cstSegmentation.state.getSegmentation).toHaveBeenCalledTimes(2);
expect(cstSegmentation.state.getSegmentation).toHaveBeenCalledWith(segmentationId);
expect(cstSegmentation.updateSegmentations).toHaveBeenCalledTimes(1);
@@ -2825,7 +2830,8 @@ describe('SegmentationService', () => {
segmentIndex,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
{}
{},
false
);
expect(window.requestAnimationFrame).not.toHaveBeenCalledTimes(3);
@@ -81,6 +81,10 @@ const EVENTS = {
SEGMENT_LOADING_COMPLETE: 'event::segment_loading_complete',
// loading completed for all segments
SEGMENTATION_LOADING_COMPLETE: 'event::segmentation_loading_complete',
// fired when a contour annotation cut merge process is completed
SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED:
'event::annotation_cut_merge_process_completed',
SEGMENTATION_STYLE_MODIFIED: 'event::segmentation_style_modified',
};
const VALUE_TYPES = {};
@@ -274,11 +278,13 @@ class SegmentationService extends PubSubService {
viewportId: string,
{
segmentationId,
predecessorImageId,
type,
config,
suppressEvents = false,
}: {
segmentationId: string;
predecessorImageId?: string;
type?: csToolsEnums.SegmentationRepresentations;
config?: {
blendMode?: csEnums.BlendModes;
@@ -287,6 +293,9 @@ class SegmentationService extends PubSubService {
}
): Promise<void> {
const segmentation = this.getSegmentation(segmentationId);
if (segmentation && !segmentation.predecessorImageId && predecessorImageId) {
segmentation.predecessorImageId = predecessorImageId;
}
const csViewport = this.getAndValidateViewport(viewportId);
if (!csViewport) {
@@ -350,6 +359,39 @@ class SegmentationService extends PubSubService {
FrameOfReferenceUID?: string;
label?: string;
}
): Promise<string> {
return this._createSegmentationForDisplaySet(displaySet, LABELMAP, options);
}
public async createContourForDisplaySet(
displaySet: AppTypes.DisplaySet,
options?: {
segmentationId?: string;
segments?: { [segmentIndex: number]: Partial<cstTypes.Segment> };
FrameOfReferenceUID?: string;
label?: string;
}
): Promise<string> {
return this._createSegmentationForDisplaySet(displaySet, CONTOUR, options);
}
/**
* Private method to create segmentation for a display set with the specified type
*
* @param displaySet - The display set to create the segmentation for
* @param segmentationType - The type of segmentation (SegmentationRepresentations enum)
* @param options - Optional parameters for creating the segmentation
* @returns A promise that resolves to the created segmentation ID
*/
private async _createSegmentationForDisplaySet(
displaySet: AppTypes.DisplaySet,
segmentationType: SegmentationRepresentations,
options?: {
segmentationId?: string;
segments?: { [segmentIndex: number]: Partial<cstTypes.Segment> };
FrameOfReferenceUID?: string;
label?: string;
}
): Promise<string> {
// Todo: random does not makes sense, make this better, like
// labelmap 1, 2, 3 etc
@@ -375,7 +417,7 @@ class SegmentationService extends PubSubService {
const segmentationPublicInput: cstTypes.SegmentationPublicInput = {
segmentationId,
representation: {
type: LABELMAP,
type: segmentationType,
data: {
imageIds: segImageIds,
// referencedVolumeId: this._getVolumeIdForDisplaySet(displaySet),
@@ -799,15 +841,30 @@ class SegmentationService extends PubSubService {
segmentationId?: string;
segmentIndex?: number;
},
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle
style: cstTypes.LabelmapStyle | cstTypes.ContourStyle | cstTypes.SurfaceStyle,
merge: boolean = true
) => {
cstSegmentation.config.style.setStyle(specifier, style);
cstSegmentation.config.style.setStyle(specifier, style, merge);
this._broadcastEvent(EVENTS.SEGMENTATION_STYLE_MODIFIED, {
specifier,
style,
merge,
});
};
public resetToGlobalStyle = () => {
cstSegmentation.config.style.resetToGlobalStyle();
};
public getNextAvailableSegmentIndex(segmentationId: string): number {
const csSegmentation = this.getCornerstoneSegmentation(segmentationId);
// grab the next available segment index based on the object keys,
// so basically get the highest segment index value + 1
const segmentKeys = Object.keys(csSegmentation.segments);
return segmentKeys.length === 0 ? 1 : Math.max(...segmentKeys.map(Number)) + 1;
}
/**
* Adds a new segment to the specified segmentation.
* @param segmentationId - The ID of the segmentation to add the segment to.
@@ -841,10 +898,7 @@ class SegmentationService extends PubSubService {
let segmentIndex = config.segmentIndex;
if (!segmentIndex) {
// grab the next available segment index based on the object keys,
// so basically get the highest segment index value + 1
const segmentKeys = Object.keys(csSegmentation.segments);
segmentIndex = segmentKeys.length === 0 ? 1 : Math.max(...segmentKeys.map(Number)) + 1;
segmentIndex = this.getNextAvailableSegmentIndex(segmentationId);
}
// update the segmentation
@@ -1627,6 +1681,11 @@ class SegmentationService extends PubSubService {
csToolsEnums.Events.SEGMENTATION_ADDED,
this._onSegmentationAddedFromSource
);
eventTarget.addEventListener(
csToolsEnums.Events.ANNOTATION_CUT_MERGE_PROCESS_COMPLETED,
this._onAnnotationCutMergeProcessCompletedFromSource
);
}
private getCornerstoneSegmentation(segmentationId: string) {
@@ -1650,13 +1709,6 @@ class SegmentationService extends PubSubService {
if (hideOthers) {
throw new Error('hideOthers is not working right now');
for (let i = 0; i < segments.length; i++) {
if (i !== segmentIndex) {
newSegmentSpecificConfig[i] = {
fillAlpha: 0,
};
}
}
}
const { fillAlpha } = this.getStyle({
@@ -1697,7 +1749,8 @@ class SegmentationService extends PubSubService {
segmentIndex,
type: LABELMAP,
},
{}
{},
false
);
}
};
@@ -1879,6 +1932,13 @@ class SegmentationService extends PubSubService {
segmentationId,
});
};
private _onAnnotationCutMergeProcessCompletedFromSource = evt => {
const { segmentationId } = evt.detail;
this._broadcastEvent(this.EVENTS.SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED, {
segmentationId,
});
};
}
export default SegmentationService;
@@ -43,7 +43,7 @@ const segmentationRepresentationModifiedCallback = async (
) => {
const event = sourceEvent as ToolsTypes.EventTypes.SegmentationRepresentationModifiedEventType;
const { segmentationId } = event.detail;
const { segmentationId, type: segmentationRepresentationType } = event.detail;
const { segmentationService } = servicesManager.services;
const targetViewportId = targetViewport.viewportId;
@@ -69,7 +69,8 @@ const segmentationRepresentationModifiedCallback = async (
const type: Enums.SegmentationRepresentations =
viewport.type === CoreEnums.ViewportType.VOLUME_3D
? Enums.SegmentationRepresentations.Surface
: Enums.SegmentationRepresentations.Labelmap;
: ((segmentationRepresentationType as Enums.SegmentationRepresentations) ??
Enums.SegmentationRepresentations.Labelmap);
await segmentationService.addSegmentationRepresentation(targetViewportId, {
segmentationId,
@@ -714,7 +714,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
// is being used to navigate to the initial view position for measurement
// navigation and other navigation forcing specific views.
let initialImageIndexToUse =
presentations?.positionPresentation?.initialImageIndex ?? <number>initialImageIndex;
presentations?.positionPresentation?.initialImageIndex ?? (initialImageIndex as number);
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
@@ -933,6 +933,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
const displaySetModality = displaySet?.Modality;
// seems like a hack but we need the actor to be ready first before
// we set the properties
const timeoutViewportCallback = (callback: () => void) => setTimeout(callback, 0);
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
const filteredVolumeInputArray = volumeInputArray
.map((volumeInput, index) => {
@@ -984,12 +988,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const backgroundDisplaySet = displaySetService.getDisplaySetsBy(
displaySet =>
!displaySet.isOverlayDisplaySet &&
displaySet.images.some(image => image.imageId === sampleImageId)
displaySet.images?.some(image => image.imageId === sampleImageId)
);
if (backgroundDisplaySet.length !== 1) {
throw new Error('Background display set not found');
}
if (viewport.type === csEnums.ViewportType.VOLUME_3D) {
timeoutViewportCallback(() => {
viewportGridService.setDisplaySetsForViewport({
viewportId: viewport.id,
displaySetInstanceUIDs: [backgroundDisplaySet[0].displaySetInstanceUID],
});
});
}
}
});
}
@@ -1006,15 +1019,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
viewport.render();
volumesProperties.forEach(({ properties, volumeId }) => {
setTimeout(() => {
// seems like a hack but we need the actor to be ready first before
// we set the properties
timeoutViewportCallback(() => {
viewport.setProperties(properties, volumeId);
viewport.render();
}, 0);
});
});
this.setPresentations(viewport.id, presentations, viewportInfo);
this.setPresentations(viewport.id, presentations);
if (!presentations.positionPresentation) {
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
@@ -1079,8 +1090,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
? csToolsEnums.SegmentationRepresentations.Labelmap
: csToolsEnums.SegmentationRepresentations.Contour;
const { predecessorImageId } = displaySet;
segmentationService.addSegmentationRepresentation(viewport.id, {
segmentationId,
predecessorImageId,
type: representationType,
});
@@ -2,3 +2,4 @@ export { useLutPresentationStore } from './useLutPresentationStore';
export { usePositionPresentationStore } from './usePositionPresentationStore';
export { useSegmentationPresentationStore } from './useSegmentationPresentationStore';
export { useSynchronizersStore } from './useSynchronizersStore';
export { useSelectedSegmentationsForViewportStore } from './useSelectedSegmentationsForViewportStore';
@@ -0,0 +1,85 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { SelectedSegmentationByTypeMap } from '../types/SelectedSegmentationByTypeMap';
const PRESENTATION_TYPE_ID = 'selectedSegmentationsForViewportId';
const DEBUG_STORE = false;
/**
* Represents the state and actions for managing selected segmentations for a viewport by representation type.
*/
type SelectedSegmentationsForViewportState = {
/**
* Stores a mapping from viewport id to a map of representation type to segmentation id.
*/
selectedSegmentationsForViewport: Record<string, SelectedSegmentationByTypeMap>;
/**
* Sets the selected segmentations for a given viewport id.
*
* @param key - The viewport id.
* @param value - The `SelectedSegmentationByTypeMap` to associate with the viewport id.
*/
setSelectedSegmentationsForViewport: (key: string, value: SelectedSegmentationByTypeMap) => void;
/**
* Clears all selected segmentations for all viewports.
*/
clearSelectedSegmentationsForViewportStore: () => void;
/**
* Type identifier for the store.
*/
type: string;
};
/**
* Creates the Selected Segmentations For Viewport store.
*
* @param set - The zustand set function.
* @returns The selected segmentations for viewport store state and actions.
*/
const createSelectedSegmentationsForViewportStore = (
set
): SelectedSegmentationsForViewportState => ({
selectedSegmentationsForViewport: {},
type: PRESENTATION_TYPE_ID,
/**
* Sets the selected segmentations for a given viewport id.
*/
setSelectedSegmentationsForViewport: (key, value) =>
set(
state => ({
selectedSegmentationsForViewport: {
...state.selectedSegmentationsForViewport,
[key]: value,
},
}),
false,
'setSelectedSegmentationsForViewport'
),
/**
* Clears all selected segmentations for all viewports.
*/
clearSelectedSegmentationsForViewportStore: () =>
set(
{ selectedSegmentationsForViewport: {} },
false,
'clearSelectedSegmentationsForViewportStore'
),
});
/**
* Zustand store for managing selected segmentations for a viewport by representation type.
* Applies devtools middleware when DEBUG_STORE is enabled.
*/
export const useSelectedSegmentationsForViewportStore =
create<SelectedSegmentationsForViewportState>()(
DEBUG_STORE
? devtools(createSelectedSegmentationsForViewportStore, {
name: 'SelectedSegmentationsForViewportStore',
})
: createSelectedSegmentationsForViewportStore
);
@@ -0,0 +1,3 @@
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
export type SelectedSegmentationByTypeMap = Map<SegmentationRepresentations, string>;
@@ -1,3 +1,4 @@
import { utils } from '@ohif/core';
import React, { useEffect, useState } from 'react';
import html2canvas from 'html2canvas';
import { getEnabledElement, StackViewport, BaseVolumeViewport } from '@cornerstonejs/core';
@@ -5,6 +6,8 @@ import { ToolGroupManager, segmentation, Enums } from '@cornerstonejs/tools';
import { getEnabledElement as OHIFgetEnabledElement } from '../state';
import { useSystem } from '@ohif/core/src';
const { downloadUrl } = utils;
const DEFAULT_SIZE = 512;
const MAX_TEXTURE_SIZE = 10000;
const VIEWPORT_ID = 'cornerstone-viewport-download-form';
@@ -218,10 +221,7 @@ const CornerstoneViewportDownloadForm = ({
}
const canvas = await html2canvas(divForDownloadViewport as HTMLElement);
const link = document.createElement('a');
link.download = `${filename}.${fileType}`;
link.href = canvas.toDataURL(`image/${fileType}`, 1.0);
link.click();
downloadUrl(canvas.toDataURL(`image/${fileType}`, 1.0));
};
const ViewportDownloadFormNew = customizationService.getCustomization(
@@ -0,0 +1,74 @@
import { utilities as csUtils } from '@cornerstonejs/core';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import i18n from '@ohif/i18n';
import { ServicesManager } from '@ohif/core';
function _createDefaultSegments(createInitialSegment?: boolean) {
return createInitialSegment
? {
1: {
label: `${i18n.t('Tools:Segment')} 1`,
active: true,
},
}
: {};
}
type CreateSegmentationForViewportOptions = {
displaySetInstanceUID?: string;
label?: string;
segmentationId?: string;
createInitialSegment?: boolean;
};
type CreateSegmentationForViewportParams = {
viewportId: string;
options?: CreateSegmentationForViewportOptions;
segmentationType: SegmentationRepresentations;
};
/**
* Creates a segmentation for the active viewport
*
* The created segmentation will be registered as a display set and also added
* as a segmentation representation to the viewport.
*/
export async function createSegmentationForViewport(
servicesManager: ServicesManager,
{ viewportId, options = {}, segmentationType }: CreateSegmentationForViewportParams
): Promise<string> {
const { viewportGridService, displaySetService, segmentationService } = servicesManager.services;
const { viewports } = viewportGridService.getState();
const targetViewportId = viewportId;
const viewport = viewports.get(targetViewportId);
// Todo: add support for multiple display sets
const displaySetInstanceUID = options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
const segs = segmentationService.getSegmentations();
const label = options.label || `${i18n.t('Tools:Segmentation')} ${segs.length + 1}`;
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
const segmentationCreationOptions = {
label,
segmentationId,
segments: _createDefaultSegments(options.createInitialSegment),
};
// This will create the segmentation and register it as a display set
const generatedSegmentationId = await (segmentationType === SegmentationRepresentations.Labelmap
? segmentationService.createLabelmapForDisplaySet(displaySet, segmentationCreationOptions)
: segmentationService.createContourForDisplaySet(displaySet, segmentationCreationOptions));
// Also add the segmentation representation to the viewport
await segmentationService.addSegmentationRepresentation(viewportId, {
segmentationId,
type: segmentationType,
});
return generatedSegmentationId;
}
@@ -13,6 +13,7 @@ Object.defineProperty(global, 'URL', {
writable: true,
value: {
createObjectURL: jest.fn().mockReturnValue('mock-url'),
revokeObjectURL: () => undefined,
},
});
@@ -1,3 +1,7 @@
import { utils } from '@ohif/core';
const { downloadCsv } = utils;
export function generateSegmentationCSVReport(
segmentationData,
info: {
@@ -121,19 +125,7 @@ export function generateSegmentationCSVReport(
csvString += formattedRow.join(',') + '\n';
}
// Create a download link and trigger the download
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute(
'download',
`${segmentationData.label || 'Segmentation'}_Report_${new Date().toISOString().split('T')[0]}.csv`
);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
downloadCsv(csvString, {
filename: `${segmentationData.label || 'Segmentation'}_Report_${new Date().toISOString().split('T')[0]}.csv`,
});
}
@@ -70,43 +70,6 @@ describe('getUpdatedViewportsForSegmentation', () => {
expect(result).toEqual(mockUpdatedViewports);
});
it('should filter out volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume',
},
},
]);
});
it('should handle viewports without viewportOptions', () => {
const mockUpdatedViewports = [
{
@@ -255,70 +218,22 @@ describe('getUpdatedViewportsForSegmentation', () => {
it('should handle getViewportsRequireUpdate returning null', () => {
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(null);
expect(() => getUpdatedViewportsForSegmentation(defaultParameters)).toThrow();
expect(getUpdatedViewportsForSegmentation(defaultParameters)).toEqual(null);
});
it('should handle mixed viewport types including volume3d', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
{ viewportOptions: { viewportType: 'stack' } },
{ viewportOptions: { viewportType: 'volume3d' } },
{ viewportOptions: { viewportType: 'volume3d' } },
{ viewportOptions: { viewportType: 'orthogonal' } },
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([
{
viewportOptions: {
viewportType: 'stack',
},
},
{
viewportOptions: {
viewportType: 'orthogonal',
},
},
]);
});
it('should handle all volume3d viewports', () => {
const mockUpdatedViewports = [
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
];
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue(mockUpdatedViewports);
const result = getUpdatedViewportsForSegmentation(defaultParameters);
expect(result).toEqual([]);
expect(result).toEqual(mockUpdatedViewports);
});
it('should handle viewports with undefined viewportType', () => {
@@ -328,6 +243,11 @@ describe('getUpdatedViewportsForSegmentation', () => {
viewportType: undefined,
},
},
{
viewportOptions: {
viewportType: 'volume3d',
},
},
{
viewportOptions: {
someOtherProperty: 'value',
@@ -16,7 +16,7 @@ function getUpdatedViewportsForSegmentation({
isHangingProtocolLayout
);
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
return updatedViewports;
}
const getTargetViewport = ({ viewportId, viewportGridService }) => {
@@ -10,6 +10,7 @@ import promptHydrationDialog, {
HydrationSRResult,
} from './promptHydrationDialog';
import { getCenterExtent } from './getCenterExtent';
import { createSegmentationForViewport } from './createSegmentationForViewport';
const utils = {
handleSegmentChange,
@@ -18,6 +19,7 @@ const utils = {
setupSegmentationModifiedHandler,
promptHydrationDialog,
getCenterExtent,
createSegmentationForViewport,
};
export type { HydrationDialogProps, HydrationCallback, HydrationSRResult };
@@ -84,7 +84,7 @@ export default function interleaveCenterLoader({
// the imageIds and save them in AllRequests for later use
const AllRequests = [];
volumes.forEach(volume => {
const requests = volume.getImageLoadRequests();
const requests = volume.getImageLoadRequests?.() ?? [];
if (!requests.length || !requests[0] || !requests[0].imageId) {
return;
@@ -83,7 +83,7 @@ export default function interleaveTopToBottom({
// the imageIds and save them in AllRequests for later use
const AllRequests = [];
volumes.forEach(volume => {
const requests = volume.getImageLoadRequests();
const requests = volume.getImageLoadRequests?.() ?? [];
if (!requests?.[0]?.imageId) {
return;
@@ -107,8 +107,8 @@ function getMappedAnnotations(annotation, displaySetService) {
const displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0];
const { SeriesNumber } = displaySet;
const { value } = targetStats;
const unit = 'HU';
const { value, modalityUnit } = targetStats;
const unit = modalityUnit || '';
annotations.push({
SeriesInstanceUID,
@@ -13,7 +13,6 @@ const supportedTools = [
'LivewireContour',
'UltrasoundDirectionalTool',
'UltrasoundPleuraBLineTool',
'SCOORD3DPoint',
'SegmentBidirectional',
];
@@ -0,0 +1,86 @@
import { cache, metaData } from '@cornerstonejs/core';
import { Types as cstTypes } from '@cornerstonejs/tools';
/**
* Checks if a labelmap segmentation has exportable data
* A label map is exportable if it has any pixel data and
* it is referenced by a display set that is reconstructable.
*
* @param labelmap - The labelmap representation data
* @param displaySetService - The display set service instance
* @returns boolean - Whether the labelmap has exportable data
*/
export const hasExportableLabelMapData = (
labelmap: cstTypes.LabelmapSegmentationData | undefined,
displaySetService: any
): boolean => {
if (!labelmap) {
return false;
}
const imageIds = labelmap?.imageIds;
if (!imageIds?.length) {
return false;
}
const hasPixelData = imageIds.some(imageId => {
const pixelData = cache.getImage(imageId)?.getPixelData();
if (!pixelData) {
return false;
}
for (let i = 0; i < pixelData.length; i++) {
if (pixelData[i] !== 0) {
return true;
}
}
});
if (!hasPixelData) {
return false;
}
const referencedImageIds = labelmap?.referencedImageIds;
if (!referencedImageIds) {
return false;
}
const firstImageId = referencedImageIds[0];
const instance = metaData.get('instance', firstImageId);
if (!instance) {
return false;
}
const SOPInstanceUID = instance.SOPInstanceUID || instance.SopInstanceUID;
const SeriesInstanceUID = instance.SeriesInstanceUID;
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID
);
return !!displaySet;
};
/**
* Checks if a contour segmentation has exportable data
* A contour is exportable if it has any contour annotation/geometry data.
*
* @param contour - The contour representation data
* @returns boolean - Whether the contour has exportable data
*/
export const hasExportableContourData = (
contour: cstTypes.ContourSegmentationData | undefined
): boolean => {
if (!contour) {
return false;
}
const contourAnnotationUIDsMap = contour?.annotationUIDsMap;
if (!contourAnnotationUIDsMap) {
return false;
}
return contourAnnotationUIDsMap.size > 0;
};
@@ -1,5 +1,7 @@
import * as cornerstoneTools from '@cornerstonejs/tools';
import { updateSegmentationStats } from './updateSegmentationStats';
import { useSelectedSegmentationsForViewportStore } from '../stores';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
/**
* Sets up the handler for segmentation data modification events
@@ -110,3 +112,49 @@ export function setupSegmentationModifiedHandler({ segmentationService }) {
return { unsubscribe };
}
/**
* Sets up auto tab switching for when the first segmentation is added into the viewer.
*/
export function setUpSelectedSegmentationsForViewportHandler({ segmentationService }) {
const selectedSegmentationsForViewportEvents = [
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED,
];
const unsubscribeSelectedSegmentationsForViewportEvents = selectedSegmentationsForViewportEvents
.map(eventName =>
segmentationService.subscribe(eventName, event => {
const { viewportId } = event;
if (!viewportId) {
return;
}
const { selectedSegmentationsForViewport, setSelectedSegmentationsForViewport } =
useSelectedSegmentationsForViewportStore.getState();
const representations = segmentationService.getSegmentationRepresentations(viewportId);
const activeRepresentation = representations.find(representation => representation.active);
const typeToSegmentationIdMap =
selectedSegmentationsForViewport[viewportId] ??
new Map<SegmentationRepresentations, string>();
if (activeRepresentation) {
typeToSegmentationIdMap.set(
activeRepresentation.type,
activeRepresentation.segmentationId
);
} else {
typeToSegmentationIdMap.clear();
}
setSelectedSegmentationsForViewport(viewportId, typeToSegmentationIdMap);
})
)
.map(subscription => subscription.unsubscribe);
return { unsubscribeSelectedSegmentationsForViewportEvents };
}
@@ -0,0 +1,26 @@
import * as csTools from '@cornerstonejs/tools';
import { eventTarget } from '@cornerstonejs/core';
const { Enums: csToolsEnums } = csTools;
const _autoAcceptAnnotationInterpolationHandler = evt => {
const { element } = evt.detail;
csTools.utilities.contours.acceptAutogeneratedInterpolations(element, {});
};
export const setUpAnnotationEventHandlers = (): [() => void] => {
eventTarget.addEventListener(
csToolsEnums.Events.ANNOTATION_INTERPOLATION_PROCESS_COMPLETED,
_autoAcceptAnnotationInterpolationHandler
);
const annotationInterpolationProcessCompletedUnsubscribe = () => {
eventTarget.removeEventListener(
csToolsEnums.Events.ANNOTATION_INTERPOLATION_PROCESS_COMPLETED,
_autoAcceptAnnotationInterpolationHandler
);
};
return [annotationInterpolationProcessCompletedUnsubscribe];
};
@@ -2,11 +2,13 @@ import { setUpSegmentationEventHandlers } from './setUpSegmentationEventHandlers
import {
setupSegmentationDataModifiedHandler,
setupSegmentationModifiedHandler,
setUpSelectedSegmentationsForViewportHandler,
} from './segmentationHandlers';
jest.mock('./segmentationHandlers', () => ({
setupSegmentationDataModifiedHandler: jest.fn(),
setupSegmentationModifiedHandler: jest.fn(),
setUpSelectedSegmentationsForViewportHandler: jest.fn(),
}));
describe('setUpSegmentationEventHandlers', () => {
@@ -38,6 +40,7 @@ describe('setUpSegmentationEventHandlers', () => {
const mockUnsubscribeDataModified = jest.fn();
const mockUnsubscribeModified = jest.fn();
const mockUnsubscribeCreated = jest.fn();
const mockUnsubscribeSelectedSegmentationsForViewportEvents = [jest.fn(), jest.fn()];
const defaultParameters = {
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
@@ -52,6 +55,10 @@ describe('setUpSegmentationEventHandlers', () => {
(setupSegmentationModifiedHandler as jest.Mock).mockReturnValue({
unsubscribe: mockUnsubscribeModified,
});
(setUpSelectedSegmentationsForViewportHandler as jest.Mock).mockReturnValue({
unsubscribeSelectedSegmentationsForViewportEvents:
mockUnsubscribeSelectedSegmentationsForViewportEvents,
});
mockSegmentationService.subscribe.mockReturnValue({
unsubscribe: mockUnsubscribeCreated,
});
@@ -92,6 +99,7 @@ describe('setUpSegmentationEventHandlers', () => {
mockUnsubscribeDataModified,
mockUnsubscribeModified,
mockUnsubscribeCreated,
...mockUnsubscribeSelectedSegmentationsForViewportEvents,
],
});
});
@@ -117,6 +125,7 @@ describe('setUpSegmentationEventHandlers', () => {
it('should create and add display set when segmentation is added and no displaySet exists', () => {
const mockSegmentation = {
label: 'Test Segmentation Label',
cachedStats: {
info: 'Test Segmentation Label',
},
@@ -143,12 +152,12 @@ describe('setUpSegmentationEventHandlers', () => {
displaySetInstanceUID: 'test-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
SeriesDescription: mockSegmentation.label,
Modality: 'SEG',
numImageFrames: mockSegmentation.representationData.Labelmap.imageIds.length,
imageIds: mockSegmentation.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
label: mockSegmentation.label,
madeInClient: true,
segmentationId: 'test-segmentation-id',
isDerived: true,
@@ -157,6 +166,7 @@ describe('setUpSegmentationEventHandlers', () => {
it('should handle displaySet undefined when segmentation is added', () => {
const mockSegmentation = {
label: 'Test Segmentation Label',
cachedStats: {
info: 'Test Segmentation Label',
},
@@ -182,12 +192,12 @@ describe('setUpSegmentationEventHandlers', () => {
displaySetInstanceUID: 'test-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
SeriesDescription: mockSegmentation.label,
Modality: 'SEG',
numImageFrames: mockSegmentation.representationData.Labelmap.imageIds.length,
imageIds: mockSegmentation.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
label: mockSegmentation.label,
madeInClient: true,
segmentationId: 'test-segmentation-id',
isDerived: true,
@@ -196,6 +206,7 @@ describe('setUpSegmentationEventHandlers', () => {
it('should handle empty imageIds array', () => {
const mockSegmentation = {
label: 'Empty Segmentation',
cachedStats: {
info: 'Empty Segmentation',
},
@@ -220,12 +231,12 @@ describe('setUpSegmentationEventHandlers', () => {
displaySetInstanceUID: 'empty-segmentation-id',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation.cachedStats.info,
SeriesDescription: mockSegmentation.label,
Modality: 'SEG',
numImageFrames: 0,
imageIds: [],
isOverlayDisplaySet: true,
label: mockSegmentation.cachedStats.info,
label: mockSegmentation.label,
madeInClient: true,
segmentationId: 'empty-segmentation-id',
isDerived: true,
@@ -234,6 +245,7 @@ describe('setUpSegmentationEventHandlers', () => {
it('should handle different segmentation label values', () => {
const mockSegmentation = {
label: 'Custom Label Text',
cachedStats: {
info: 'Custom Label Text',
},
@@ -272,6 +284,7 @@ describe('setUpSegmentationEventHandlers', () => {
it('should handle multiple segmentation events', () => {
const mockSegmentation1 = {
label: 'Segmentation 1',
cachedStats: {
info: 'Segmentation 1',
},
@@ -283,6 +296,7 @@ describe('setUpSegmentationEventHandlers', () => {
};
const mockSegmentation2 = {
label: 'Segmentation 2',
cachedStats: {
info: 'Segmentation 2',
},
@@ -310,12 +324,12 @@ describe('setUpSegmentationEventHandlers', () => {
displaySetInstanceUID: 'segmentation-1',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation1.cachedStats.info,
SeriesDescription: mockSegmentation1.label,
Modality: 'SEG',
numImageFrames: 1,
imageIds: mockSegmentation1.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation1.cachedStats.info,
label: mockSegmentation1.label,
madeInClient: true,
segmentationId: 'segmentation-1',
isDerived: true,
@@ -324,12 +338,12 @@ describe('setUpSegmentationEventHandlers', () => {
displaySetInstanceUID: 'segmentation-2',
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: mockSegmentation2.cachedStats.info,
SeriesDescription: mockSegmentation2.label,
Modality: 'SEG',
numImageFrames: 2,
imageIds: mockSegmentation2.representationData.Labelmap.imageIds,
isOverlayDisplaySet: true,
label: mockSegmentation2.cachedStats.info,
label: mockSegmentation2.label,
madeInClient: true,
segmentationId: 'segmentation-2',
isDerived: true,
@@ -1,4 +1,7 @@
import { Enums as csToolsEnums } from '@cornerstonejs/tools';
import {
setUpSelectedSegmentationsForViewportHandler,
setupSegmentationDataModifiedHandler,
setupSegmentationModifiedHandler,
} from './segmentationHandlers';
@@ -27,8 +30,10 @@ export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManage
}
const segmentation = segmentationService.getSegmentation(segmentationId);
const label = segmentation.cachedStats.info;
const imageIds = segmentation.representationData.Labelmap.imageIds;
const label = segmentation.label;
const imageIds =
segmentation.representationData?.Labelmap?.imageIds ??
segmentation.representationData?.Contour?.imageIds;
// Create a display set for the segmentation
const segmentationDisplaySet = {
@@ -36,7 +41,9 @@ export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManage
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
SeriesDescription: label,
Modality: 'SEG',
Modality: segmentation.representationData[csToolsEnums.SegmentationRepresentations.Contour]
? 'RTSTRUCT'
: 'SEG',
numImageFrames: imageIds.length,
imageIds,
isOverlayDisplaySet: true,
@@ -50,10 +57,16 @@ export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManage
}
);
const { unsubscribeSelectedSegmentationsForViewportEvents } =
setUpSelectedSegmentationsForViewportHandler({
segmentationService,
});
const unsubscriptions = [
unsubscribeSegmentationDataModifiedHandler,
unsubscribeSegmentationModifiedHandler,
unsubscribeSegmentationCreated,
...unsubscribeSelectedSegmentationsForViewportEvents,
];
return { unsubscriptions };
@@ -84,6 +84,10 @@ function groupViewportsByModality(
return viewportsArray.reduce((acc, viewport) => {
const { displaySetInstanceUIDs } = viewport;
// Todo: add proper fusion support
// Fix: Skip processing if the viewport is empty.
if (!displaySetInstanceUIDs?.length) {
return acc;
}
const displaySetInstanceUID = displaySetInstanceUIDs[0];
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
@@ -348,20 +348,4 @@ describe('Transitions Module', () => {
});
});
});
describe('Performance Validation', () => {
it('should handle many rapid calls without issues', () => {
const startTime = performance.now();
for (let i = 0; i < 1000; i++) {
const t = i / 1000;
linear(t);
easeInOut(t, 0.1);
ease(t, 0.2, 1.5);
}
const endTime = performance.now();
expect(endTime - startTime).toBeLessThan(10);
});
});
});
+248
View File
@@ -3,6 +3,254 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.12.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.96...v3.12.0-beta.97) (2025-12-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.95...v3.12.0-beta.96) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.94...v3.12.0-beta.95) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.93...v3.12.0-beta.94) (2025-12-02)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.92...v3.12.0-beta.93) (2025-11-25)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.91...v3.12.0-beta.92) (2025-11-24)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.90...v3.12.0-beta.91) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.89...v3.12.0-beta.90) (2025-11-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.88...v3.12.0-beta.89) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.87...v3.12.0-beta.88) (2025-11-20)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.86...v3.12.0-beta.87) (2025-11-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.85...v3.12.0-beta.86) (2025-11-13)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.84...v3.12.0-beta.85) (2025-11-11)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.83...v3.12.0-beta.84) (2025-11-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.82...v3.12.0-beta.83) (2025-11-06)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.81...v3.12.0-beta.82) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.80...v3.12.0-beta.81) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.79...v3.12.0-beta.80) (2025-11-05)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.78...v3.12.0-beta.79) (2025-10-30)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.77...v3.12.0-beta.78) (2025-10-29)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.76...v3.12.0-beta.77) (2025-10-28)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.75...v3.12.0-beta.76) (2025-10-27)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.74...v3.12.0-beta.75) (2025-10-24)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.73...v3.12.0-beta.74) (2025-10-23)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.72...v3.12.0-beta.73) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.71...v3.12.0-beta.72) (2025-10-22)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.70...v3.12.0-beta.71) (2025-10-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.69...v3.12.0-beta.70) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.68...v3.12.0-beta.69) (2025-10-20)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.67...v3.12.0-beta.68) (2025-10-18)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.66...v3.12.0-beta.67) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-default
# [3.12.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.12.0-beta.65...v3.12.0-beta.66) (2025-10-17)
**Note:** Version bump only for package @ohif/extension-default
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-default",
"version": "3.12.0-beta.66",
"version": "3.12.0-beta.97",
"description": "Common/default features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@@ -34,9 +34,9 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.12.0-beta.66",
"@ohif/i18n": "3.12.0-beta.66",
"dcmjs": "0.43.1",
"@ohif/core": "3.12.0-beta.97",
"@ohif/i18n": "3.12.0-beta.97",
"dcmjs": "0.45.0",
"dicomweb-client": "0.10.4",
"prop-types": "15.8.1",
"react": "18.3.1",
@@ -28,7 +28,7 @@ const SidePanelWithServices = ({
onClose,
...props
}: SidePanelWithServicesProps) => {
const panelService = servicesManager?.services?.panelService;
const { panelService, toolbarService, viewportGridService } = servicesManager.services;
// Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM.
// Thus going to the Study List page and back to the viewer resets this flag for a SidePanel.
@@ -37,9 +37,15 @@ const SidePanelWithServices = ({
const [closedManually, setClosedManually] = useState(false);
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
setActiveTabIndex(activeTabIndex);
}, []);
const handleActiveTabIndexChange = useCallback(
({ activeTabIndex }) => {
const { activeViewportId: viewportId } = viewportGridService.getState();
toolbarService.refreshToolbarState({ viewportId });
setActiveTabIndex(activeTabIndex);
},
[toolbarService, viewportGridService]
);
const handleOpen = useCallback(() => {
setSidePanelExpanded(true);
@@ -28,7 +28,8 @@ function PanelStudyBrowser({
const { servicesManager, commandsManager, extensionManager } = useSystem();
const { displaySetService, customizationService } = servicesManager.services;
const navigate = useNavigate();
const studyMode = (customizationService.getCustomization('studyBrowser.studyMode') as string) || 'all';
const studyMode =
(customizationService.getCustomization('studyBrowser.studyMode') as string) || 'all';
const internalImageViewer = useImageViewer();
const StudyInstanceUIDs = internalImageViewer.StudyInstanceUIDs;
@@ -384,7 +385,11 @@ function PanelStudyBrowser({
const displaySetInstanceUID = jumpToDisplaySet;
// It is possible to navigate to a study not currently in view
const thumbnailLocation = _findTabAndStudyOfDisplaySet(displaySetInstanceUID, tabs, activeTabName);
const thumbnailLocation = _findTabAndStudyOfDisplaySet(
displaySetInstanceUID,
tabs,
activeTabName
);
if (!thumbnailLocation) {
return;
}
@@ -489,7 +494,7 @@ function _mapDisplaySets(displaySets, displaySetLoadingState, thumbnailImageSrcM
seriesNumber: ds.SeriesNumber,
modality: ds.Modality,
seriesDate: formatDate(ds.SeriesDate),
numInstances: ds.numImageFrames,
numInstances: ds.numImageFrames ?? ds.instances?.length,
loadingProgress,
countIcon: ds.countIcon,
messages: ds.messages,
@@ -538,11 +543,13 @@ function _findTabAndStudyOfDisplaySet(
tabs: TabsProps,
currentTabName: string
) {
const current = tabs.find(tab => tab.name===currentTabName) || tabs[0];
const current = tabs.find(tab => tab.name === currentTabName) || tabs[0];
const biasedTabs = [current, ...tabs];
for (let t = 0; t < biasedTabs.length; t++) {
const study = biasedTabs[t].studies.find(study => study.displaySets.find(ds => ds.displaySetInstanceUID ===displaySetInstanceUID));
const study = biasedTabs[t].studies.find(study =>
study.displaySets.find(ds => ds.displaySetInstanceUID === displaySetInstanceUID)
);
if (study) {
return {
tabName: biasedTabs[t].name,
@@ -1,12 +1,39 @@
import PROMPT_RESPONSES from '../utils/_shared/PROMPT_RESPONSES';
/**
* Creates and shows a report dialog prompt.
* The input for this is:
* - `title` shown in the dialog
* - `modality` being stored, used to query existing series
* - `minSeriesNumber` is the start of new series of this modality type.
* Will get set to 4000 if not determined by the modality
* - predecessorImageId is the image id that this series was currently loaded
* from. That allows defaulting the dialog to show the specified series instead
* of always creating a new series.
*
* The response is:
* - `value`, the default name of the object/series being created
* - `dataSourceName`, where to store the object to
* - `series`, is the series to store do, as referenced by a predecessorImageId value.
* - `priorSeriesNumber` is the previously lowest series number at least minSeriesNumber
* of all the seris of the given modality type.
*
* This should be provided to the DICOM encoder, which will get the predecessor
* sequence from the metaData provider so that the saved series will replace
* the existing instance in the same series.
* This will be falsy for a new series.
*/
export default function CreateReportDialogPrompt({
title = 'Create Report',
modality = 'SR',
minSeriesNumber = 0,
predecessorImageId,
extensionManager,
servicesManager,
}): Promise<{
value: string;
dataSourceName: string;
priorSeriesNumber?: number;
series: string;
action: (typeof PROMPT_RESPONSES)[keyof typeof PROMPT_RESPONSES];
}> {
@@ -16,18 +43,33 @@ export default function CreateReportDialogPrompt({
const allowMultipleDataSources = window.config.allowMultiSelectExport;
return new Promise(function (resolve, reject) {
minSeriesNumber ||=
(modality === 'SR' && 3000) ||
(modality === 'SEG' && 3100) ||
(modality === 'RTSTRUCT' && 3200) ||
4000;
return new Promise(function (resolve) {
uiDialogService.show({
id: 'report-dialog',
title,
content: ReportDialog,
contentProps: {
dataSources: allowMultipleDataSources ? dataSources : undefined,
onSave: async ({ reportName, dataSource: selectedDataSource, series }) => {
predecessorImageId,
minSeriesNumber,
modality,
onSave: async ({
reportName,
dataSource: selectedDataSource,
series,
priorSeriesNumber,
}) => {
resolve({
value: reportName,
dataSourceName: selectedDataSource,
series,
priorSeriesNumber,
action: PROMPT_RESPONSES.CREATE_REPORT,
});
},
+32 -1
View File
@@ -1,9 +1,30 @@
import React from 'react';
import { useToolbar } from '@ohif/core';
/**
* Props for the Toolbar component that renders a collection of toolbar buttons and/or button sections.
*
* @interface ToolbarProps
*/
interface ToolbarProps {
/**
* The section of buttons to display in the toolbar.
* Common values include 'primary', 'secondary', 'tertiary', etc.
* Defaults to 'primary' if not specified.
*
* @default 'primary'
*/
buttonSection?: string;
/**
* The unique identifier of the viewport this toolbar is associated with.
*/
viewportId?: string;
/**
* The numeric position or location of the toolbar.
* Used for ordering and layout purposes in the UI.
*/
location?: number;
}
@@ -60,7 +81,17 @@ export function Toolbar({ buttonSection = 'primary', viewportId, location }: Too
/>
);
return <div key={id}>{tool}</div>;
return (
<div
key={id}
// This wrapper div exists solely for React's key prop requirement during reconciliation.
// We use display:contents to make it transparent to the layout engine (children appear
// as direct children of the parent) while keeping it in the DOM for React's virtual DOM.
className="contents"
>
{tool}
</div>
);
})}
</>
);
@@ -11,28 +11,44 @@ type DataSource = {
type ReportDialogProps = {
dataSources: DataSource[];
modality?: string;
predecessorImageId?: string;
hide: () => void;
onSave: (data: { reportName: string; dataSource: string | null; series: string | null }) => void;
onSave: (data: {
reportName: string;
dataSource: string | null;
series: string | null;
priorSeriesNumber: number;
}) => void;
onCancel: () => void;
};
function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps) {
function ReportDialog({
dataSources,
modality = 'SR',
predecessorImageId,
minSeriesNumber = 3000,
hide,
onSave,
onCancel,
}: ReportDialogProps) {
const { servicesManager } = useSystem();
const [selectedDataSource, setSelectedDataSource] = useState<string | null>(
dataSources?.[0]?.value ?? null
);
const [selectedSeries, setSelectedSeries] = useState<string | null>(null);
const [reportName, setReportName] = useState('');
const { displaySetService } = servicesManager.services;
const [selectedSeries, setSelectedSeries] = useState<string | null>(predecessorImageId || null);
const [reportName, setReportName] = useState('');
const seriesOptions = useMemo(() => {
const displaySetsMap = displaySetService.getDisplaySetCache();
const displaySets = Array.from(displaySetsMap.values());
const options = displaySets
.filter(ds => ds.Modality === 'SR')
.filter(ds => ds.Modality === modality)
.map(ds => ({
value: ds.SeriesInstanceUID,
value: ds.predecessorImageId || ds.SeriesInstanceUID,
seriesNumber: isFinite(ds.SeriesNumber) ? ds.SeriesNumber : minSeriesNumber,
description: ds.SeriesDescription,
label: `${ds.SeriesDescription} ${ds.SeriesDate}/${ds.SeriesTime} ${ds.SeriesNumber}`,
}));
@@ -41,11 +57,12 @@ function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps
{
value: null,
description: null,
seriesNumber: minSeriesNumber,
label: 'Create new series',
},
...options,
];
}, [displaySetService]);
}, [displaySetService, modality]);
useEffect(() => {
const seriesOption = seriesOptions.find(s => s.value === selectedSeries);
@@ -58,6 +75,7 @@ function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps
onSave({
reportName,
dataSource: selectedDataSource,
priorSeriesNumber: Math.max(...seriesOptions.map(it => it.seriesNumber)),
series: selectedSeries,
});
hide();
@@ -32,7 +32,8 @@ export default function getDisplaySetsFromUnsupportedSeries(instances) {
SOPClassUID: instance.SOPClassUID,
SeriesDescription: instance.SeriesDescription || '',
Modality: instance.Modality,
numImageFrames: instances.length,
instances,
instance: instances[instance.length - 1],
unsupported: true,
SOPClassHandlerId: 'unsupported',
isReconstructable: false,
@@ -10,6 +10,7 @@ import ToolButtonListWrapper from './Toolbar/ToolButtonListWrapper';
import ToolRowWrapper from './Toolbar/ToolRowWrapper';
import { ToolBoxButtonGroupWrapper, ToolBoxButtonWrapper } from './Toolbar/ToolBoxWrapper';
import { ToolButtonWrapper } from './Toolbar/ToolButtonWrapper';
import { Toolbar } from './Toolbar';
export default function getToolbarModule({ commandsManager, servicesManager }: withAppTypes) {
const { cineService } = servicesManager.services;
@@ -45,6 +46,10 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
name: 'ohif.progressDropdown',
defaultComponent: ProgressDropdownWithService,
},
{
name: 'ohif.Toolbar',
defaultComponent: Toolbar,
},
{
name: 'evaluate.cine',
evaluate: () => {
+24 -41
View File
@@ -1,12 +1,21 @@
import React, { useState } from 'react';
import { Icons, PanelSection, ToolSettings } from '@ohif/ui-next';
import { useSystem, useToolbar } from '@ohif/core';
import classnames from 'classnames';
import { useSystem, useToolbar, useActiveToolOptions } from '@ohif/core';
import { useTranslation } from 'react-i18next';
interface ButtonProps {
isActive?: boolean;
options?: unknown;
/**
* Props for the Toolbox component that renders a collection of toolbar button sections.
*/
interface ToolboxProps {
/**
* The unique identifier of the button section this toolbox represents.
*/
buttonSectionId: string;
/**
* The display title for the toolbox.
*/
title: string;
}
/**
@@ -19,7 +28,7 @@ interface ButtonProps {
* role in enhancing the app with a toolbox by providing a way to integrate
* and display various tools and their corresponding options
*/
export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; title: string }) {
export function Toolbox({ buttonSectionId, title }: ToolboxProps) {
const { servicesManager } = useSystem();
const { t } = useTranslation();
@@ -30,6 +39,8 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
buttonSection: buttonSectionId,
});
const { activeToolOptions } = useActiveToolOptions({ buttonSectionId });
if (!toolboxSections.length) {
return null;
}
@@ -41,35 +52,6 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
);
}
// Helper to check a list of buttons for an active tool.
const findActiveOptions = (buttons: any[]): unknown => {
for (const tool of buttons) {
if (tool.componentProps.isActive) {
return tool.componentProps.options;
}
if (tool.componentProps.buttonSection) {
const nestedButtons = toolbarService.getButtonPropsInButtonSection(
tool.componentProps.buttonSection
) as ButtonProps[];
const activeNested = nestedButtons.find(nested => nested.isActive);
if (activeNested) {
return activeNested.options;
}
}
}
return null;
};
// Look for active tool options across all sections.
const activeToolOptions = toolboxSections.reduce((activeOptions, section) => {
if (activeOptions) {
return activeOptions;
}
const sectionId = section.componentProps.buttonSection;
const buttons = toolbarService.getButtonSection(sectionId);
return findActiveOptions(buttons);
}, null);
// Define the interaction handler once.
const handleInteraction = ({ itemId }: { itemId: string }) => {
onInteraction?.({ itemId });
@@ -103,19 +85,20 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
return (
<div
key={sectionId}
className="bg-muted flex flex-wrap space-x-2 py-2 px-1"
className="bg-muted flex flex-wrap gap-2 py-2 px-1"
>
{buttons.map(tool => {
if (!tool) {
// Skip over tools that are not visible. The visible flag is typically set to
// false as a result of the evaluator function. The evaluator might explicitly
// set visible to false. Alternatively, the ToolbarService will set the visible flag to
// false when the evaluator sets disabled to true and the tool has the hideWhenDisabled flag set to true.
if (!tool || !tool.componentProps.visible) {
return null;
}
const { id, Component, componentProps } = tool;
return (
<div
key={id}
className={classnames('ml-1')}
>
<div key={id}>
<Component
{...componentProps}
id={id}
Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff. Show more