Compare commits

...
Author SHA1 Message Date
ohif-bot 1e91285c60 chore(version): Update package versions to 3.13.0-beta.78 [skip ci] 2026-05-19 19:33:03 +00:00
Ghadeer Albattarni 27af6821b5 fix(measurements): read displayName in SplineROI and PlanarFreehandROI reports (#5908) 2026-05-19 15:28:05 -04:00
ohif-bot 6f92234111 chore(version): Update package versions to 3.13.0-beta.77 [skip ci] 2026-05-19 18:50:02 +00:00
Ghadeer Albattarni 527cfb0ae8 fix(segmentation): remove thresholdRange label in double-range tool settings (#5931) 2026-05-19 14:45:25 -04:00
ohif-bot 64fa240dfa chore(version): Update package versions to 3.13.0-beta.76 [skip ci] 2026-05-19 13:29:35 +00:00
Ghadeer Albattarni 77cd9394d2 test(segmentation): add e2e test for deleting segmentation on second reload (#6013) 2026-05-19 09:24:49 -04:00
ohif-bot 8e8f574174 chore(version): Update package versions to 3.13.0-beta.75 [skip ci] 2026-05-18 14:40:35 +00:00
Joe BoccanfusoandGhadeer Albattarni fe16e80cf3 test(e2e): update Playwright screenshots and add better tests and assertions for area calculations (#6022)
* Add (better) assertions for area calculation fixes.
* Add SVG and measurement side panel area for freehand ROI test.
* Add Playwright viewport screenshot scope migration guide.
* Update cornerstonjs dependencies to 4.22.8. Ensure all versions of @babel/preset-env are 7.29.5.

---------

Co-authored-by: Ghadeer Albattarni <165973963+GhadeerAlbattarni@users.noreply.github.com>
2026-05-18 10:36:05 -04:00
ohif-bot cb0cbe702c chore(version): Update package versions to 3.13.0-beta.74 [skip ci] 2026-05-15 19:11:45 +00:00
Joe Boccanfuso 55b46f39c6 fix(security): Update various dependencies to fix security vulnerabilities. (#6023) 2026-05-15 15:07:13 -04:00
ohif-bot 01b5c86826 chore(version): Update package versions to 3.13.0-beta.73 [skip ci] 2026-05-13 17:16:29 +00:00
Ibrahimandgreptile-apps[bot] f231eff598 feat(llm): add instructions md file for claude and other LLM tools (#6017)
* add claude instructions file

* update structure

* Update llm/AGENTS.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update llm/AGENTS.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* move to root

* symlink

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-13 13:11:55 -04:00
ohif-bot b058ec7c72 chore(version): Update package versions to 3.13.0-beta.72 [skip ci] 2026-05-12 17:26:24 +00:00
Ghadeer Albattarni 397aa4d0e3 fix(measurement-tracking): restore tracked state on undo after Delete all (#5994)
* fix: always show delete confirmation when measurements exist
2026-05-12 13:22:25 -04:00
ohif-bot 8cd8ccc163 chore(version): Update package versions to 3.13.0-beta.71 [skip ci] 2026-05-07 22:51:22 +00:00
Ghadeer Albattarni 37e19f734a fix: remove waitForVolumeLoad from main toolbar page object (#6004) 2026-05-07 18:47:27 -04:00
ohif-bot 13305de99c chore(version): Update package versions to 3.13.0-beta.70 [skip ci] 2026-05-07 19:46:54 +00:00
Ghadeer Albattarni 9cf3149d2a test(e2e): replace waitForVolumeLoad with viewport render waits (#6000) 2026-05-07 15:42:12 -04:00
ohif-bot fbea158473 chore(version): Update package versions to 3.13.0-beta.69 [skip ci] 2026-05-06 17:53:50 +00:00
Joe Boccanfuso 5e624f1387 fix(security): Patch axios security vulnerabilities. (#5998)
Patch axios security vulnerabilities.
2026-05-06 13:49:13 -04:00
ohif-bot d792bf7f80 chore(version): Update package versions to 3.13.0-beta.68 [skip ci] 2026-05-05 19:27:31 +00:00
Joe Boccanfuso 979d619f7c fix(ToolGroupService): Centralize tool binding persistence and provide API to add and remove persisted bindings. (#5989)
* Reset crosshair modifier to default when resetting user preferences.

* Apply bindings when loading persisted bindings.
2026-05-05 15:22:43 -04:00
ohif-bot 5bccf0a753 chore(version): Update package versions to 3.13.0-beta.67 [skip ci] 2026-04-30 14:18:14 +00:00
Dan Rukasandsedghi 691e26731f feat(ui-next): Adds toggle state for ToolButton and Crosshair example (#5914)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2026-04-30 10:13:58 -04:00
ohif-bot e6816f6de4 chore(version): Update package versions to 3.13.0-beta.66 [skip ci] 2026-04-29 16:01:57 +00:00
Joe Boccanfuso 468e5734a9 fix(config url): Hardening fetch options. (#5985) 2026-04-29 11:57:33 -04:00
ohif-bot 90abd00936 chore(version): Update package versions to 3.13.0-beta.65 [skip ci] 2026-04-29 14:37:25 +00:00
Ghadeer Albattarni 62620afb9b test( zoomIn-tool): verify horizontal flip is preserved in magnified view (#5983) 2026-04-29 10:32:27 -04:00
ohif-bot 03860030bd chore(version): Update package versions to 3.13.0-beta.64 [skip ci] 2026-04-27 17:00:15 +00:00
Ghadeer Albattarni f8ccf9ff2e fix(seg): prevent segmentations from spreading to all viewports before hydration confirmation in 3D four-up (#5967) 2026-04-27 12:55:12 -04:00
ohif-bot c9c4f11b82 chore(version): Update package versions to 3.13.0-beta.63 [skip ci] 2026-04-27 11:11:23 +00:00
Joe Boccanfuso f3cca21e49 fix(security): refine dynamic config URL trust policy and document trusted-origin behavior (#5973)
* fix(security): refine dynamic config URL trust policy and document trusted-origin behavior

* Update documentation for Authorization and Authentication.

* Add isSameOrigin to resolveConfigFetchPolicy return value.
2026-04-27 07:06:06 -04:00
ohif-bot 5ba8339037 chore(version): Update package versions to 3.13.0-beta.62 [skip ci] 2026-04-23 17:49:30 +00:00
Joe Boccanfuso 1fc97fea43 fix(security): Patch protobufjs for CVE-2026-41242. (#5974) 2026-04-23 13:45:09 -04:00
ohif-bot 7008029cdc chore(version): Update package versions to 3.13.0-beta.61 [skip ci] 2026-04-23 12:51:09 +00:00
Joe BoccanfusoandDan Rukas 8fc0dc16e9 feat(slice scrollbar): Integrate ViewportSliceProgressScrollbar with customizations and docs (#5960)
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2026-04-23 08:46:03 -04:00
ohif-bot 8a2fee4d8a chore(version): Update package versions to 3.13.0-beta.60 [skip ci] 2026-04-21 16:00:22 +00:00
Joe Boccanfuso eede569a88 fix(configuration): Harden dynamic datasource URL trust boundaries and credential handling. (#5963)
* fix(configuration): Harden dynamic datasource URL trust boundaries and credential handling.

* Remove testing configuration.

* Update policies for runtime ?url=... datasource loading.

* PR feedback.
2026-04-21 11:55:14 -04:00
ohif-bot 51e6b35dbb chore(version): Update package versions to 3.13.0-beta.59 [skip ci] 2026-04-16 21:51:43 +00:00
Ghadeer Albattarni 792f78e35f chore: update Cornerstone3D to v4.21.7 (#5964) 2026-04-16 17:47:00 -04:00
cc5dc20164 fix: Use newer ONNX version and load without errors (#5854)
* fix: Use newer ONNX version and load without errors

* Only changes to enable SAM again

* fix(seg hydration): auto-hydrate RT struct on second load with disableConfirmationPrompts (#5875)

* chore(version): Update package versions to 3.13.0-beta.34 [skip ci]

* fix(Threshold tool): Threshold tool no longer becomes deselected when the Dynamic option is selected (#5884)

fix(Threshold tool): Added 'ThresholdCircularBrushDynamic' to the toolNames array so the evaluator correctly recognizes it as an active state for the Threshold button when Dynamic mode is selected.

* chore(version): Update package versions to 3.13.0-beta.35 [skip ci]

* fix: Modalities in study list should select starts with as primary (#5886)

* chore(version): Update package versions to 3.13.0-beta.36 [skip ci]

* fix(security): Bump tar version to address CVE-2026-31802. (#5893)

* chore(version): Update package versions to 3.13.0-beta.37 [skip ci]

* fix(segmentation): Display "No description S:{series number} {modality}" for segmentations with no label. (#5874)

* Bump CS3D dependency to get the fallbackLabel field additions.

* chore(version): Update package versions to 3.13.0-beta.38 [skip ci]

* fix(window level): The window level value is not displayed by default on all the viewports when selecting common/custom layout and TMTV. (#5865)

* fix(window level): Set up listener for viewport availability such that the initial window level can be read and displayed.

* PR feedback.

* PR feedback.

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>

* chore(version): Update package versions to 3.13.0-beta.39 [skip ci]

* fix(security): Bump flattened version to address CVE-2026-32141. (#5897)

* chore(version): Update package versions to 3.13.0-beta.40 [skip ci]

* fix(sr-hydration): enable hydration and arrow navigation for 3D SR measurements (#5887)

Joe is away, so approving based on the code having the requested change, and otherwise looking good/passing tests.

* fix(sr-hydration): enable hydration and arrows navigation for 3D SR measurements

* test: add automated test for SR measurement navigation with arrows after hydration

* add cross-study warning in the 3D branch

* test: address reviewer feedback for the test

* fix: support 3D and 2D annotations for SR hydration

* test: improve navigation to first image

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>

* chore(version): Update package versions to 3.13.0-beta.41 [skip ci]

* feat: Add combined build (#5895)

* Add combined build

* Link script location update

* Security and validation fixes

* Allow specifying target path in PR description

* fix: Version match

* Fix build detection issue

* fix: Playwright deploy

* Separate out the branch merge guard

* Update docs and link info

* test: Update the layout change to wait for network idle

* Move audit late so the rest of the build can be worked on

* Add text with network check to ensure we see this change is updated

* Attempt to fix the mpr loading on ohif-downstream

* PR review comments

* Update docs

* Update to CS3D 4.20.0

* PR comments

* Add log on ohif-integration builds

* Update build test

* Removed unused space to kickoff build

* chore(version): Update package versions to 3.13.0-beta.42 [skip ci]

* fix(SR): Added support for spline and live wire SR items. (#5870)

* fix(SR): Added support for spline and live wire SR items.

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add a script to checkout a worktree for test builds

* fix: Allow download for testing sr validator

* Remove script that wasn't intended to be included

* Bump CS3D version.

* PR comments - simplify code and use single codepath for download

* Allow both download and save buttons for SEG and RTSTRUCT

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>

* chore(version): Update package versions to 3.13.0-beta.43 [skip ci]

* chore(tests): contour segment interactions e2e tests - rename and togglevisibility (#5891)

* chore(version): Update package versions to 3.13.0-beta.44 [skip ci]

* chore(refactor): use public appConfig getter instead of private _appConfig field (#5923)

* chore(version): Update package versions to 3.13.0-beta.45 [skip ci]

* refactor(tests): update viewport page object usage to async and update all effected tests (#5927)

* chore(version): Update package versions to 3.13.0-beta.46 [skip ci]

* fix: prevent black viewport when navigating series with client-created segmentation (#5919)

* chore(version): Update package versions to 3.13.0-beta.47 [skip ci]

* fix(measurement): Restore viewport interactivity when deleting in-progress Spline or Livewire measurement (#5905)

* chore(version): Update package versions to 3.13.0-beta.48 [skip ci]

* fix(segmentation): restrict overlay segmentation menu to same frame of reference as viewport background display set  (#5900)

- Add FrameOfReferenceUID to SEG and RTSTRUCT displaySet in SOP Class Handlers so the FOR is available for filtering
- Sync optimisticOverlayDisplaySets when background display set changes so the overlay menu reflects the correct state after a background switch
- Add FOR matching guard to the hydrate segmentation synchronizer to prevent the hydration synchronizer from blindly mirroring segmentations from a source viewport to a target viewport if their primary Frames of Reference do not align.
- fix segmentation overlay order reversal on viewport re-render

* chore(version): Update package versions to 3.13.0-beta.49 [skip ci]

* fix(security): update dependencies to fix security vulnerabilities (#5936)

* chore(version): Update package versions to 3.13.0-beta.50 [skip ci]

* fix(security): Update yarn.lock that was missed in PR #5936. (#5940)

* chore(version): Update package versions to 3.13.0-beta.51 [skip ci]

* feat(component): Adds SmartScrollbar to ui-next - OHIF-2558 (#5924)

Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>

* fix(defaultRouteInit): pass sorted display sets to hanging protocol for deterministic viewport order (#5933)

fix: pass sorted display sets to hanging protocol for deterministic viewport order

The `applyHangingProtocol` function already sorts display sets by modality
priority and series number into `sortedDisplaySets`, but the unsorted
`displaySets` array was being passed to `hangingProtocolService.run()`.

This caused non-deterministic viewport ordering across page loads because
`displaySetService.getActiveDisplaySets()` returns display sets in creation
order, which depends on asynchronous network responses.

Made-with: Cursor

* chore(version): Update package versions to 3.13.0-beta.52 [skip ci]

* revert: rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID (#5943)

* chore(version): Update package versions to 3.13.0-beta.53 [skip ci]

* fix(cornerstone): read FrameOfReferenceUID from display set in viewport service (#5950)

* chore(version): Update package versions to 3.13.0-beta.54 [skip ci]

* fix: ignore auth in git (#5955)

* chore(version): Update package versions to 3.13.0-beta.55 [skip ci]

* ONNX latest version

* chore(version): Update package versions to 3.13.0-beta.56 [skip ci]

* bun lock

* fix high sev mathjs issue

* Revert onnx changes

* Update to recent CS3D version

* Undo unneeded change

* Add null check

* Undo unneeded change

---------

Co-authored-by: Ghadeer Albattarni <165973963+GhadeerAlbattarni@users.noreply.github.com>
Co-authored-by: ohif-bot <danny.ri.brown+ohif-bot@gmail.com>
Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: diattamo <mmddiatta@gmail.com>
Co-authored-by: Pedro Köhler <pedrokohlerbh@gmail.com>
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
Co-authored-by: Alireza <ar.sedghi@gmail.com>
2026-04-15 12:04:00 -04:00
ohif-bot 31727c0668 chore(version): Update package versions to 3.13.0-beta.58 [skip ci] 2026-04-14 18:00:56 +00:00
Bill Wallace 760adc50d6 Reformat in HP MPR (#5717)
* Reformat in HP MPR

* fix: Orientation markers should work
2026-04-14 13:56:20 -04:00
ohif-bot 6e4ef68a83 chore(version): Update package versions to 3.13.0-beta.57 [skip ci] 2026-04-13 13:36:14 +00:00
Alireza f5a66b9eea fix: Enhance multiframe instance handling and improve instance valida… (#5956)
fix: Enhance multiframe instance handling and improve instance validation

- Updated `getDisplaySetInfo` to ensure local file frame imageIds resolve correctly, falling back to the source multiframe instance when necessary.
- Improved instance filtering in `isDisplaySetReconstructable` to handle undefined instances more robustly.
- Modified `App` prop types to make `routerBasename` optional and added new props for loading indicators and data sources.
- Refactored `CinePlayer` component to replace Button with a div for better accessibility and interaction.
2026-04-13 09:31:28 -04:00
ohif-bot 3069d33c99 chore(version): Update package versions to 3.13.0-beta.56 [skip ci] 2026-04-10 20:57:30 +00:00
Bill Wallace f6bbd5c779 fix: A couple of changes to enable cs3d integration build (#5944)
* fix: A couple of changes to enable cs3d integration build

* Bun update

* fix: Crosshairs tests due to order changes

* Fix a race in DicomTagBrowser.spec.ts and update the comparison for the screenshot for seg hydration.

* Fix sorting issues by using consistent sort

* fix: Inconsistency in scoord loader.  Will need an update to screenshot

* Fix crosshairs stability issues and random order issues in Scoord

* Update the comparison image

* Update navigate image

* fix: Axios issue

* Update to current CS3D
2026-04-10 16:51:37 -04:00
ohif-bot 66c8e0c195 chore(version): Update package versions to 3.13.0-beta.55 [skip ci] 2026-04-10 19:21:27 +00:00
Alireza 961eea1c39 fix: ignore auth in git (#5955) 2026-04-10 15:16:55 -04:00
ohif-bot 0ddf32eef2 chore(version): Update package versions to 3.13.0-beta.54 [skip ci] 2026-04-08 15:59:34 +00:00
Ghadeer Albattarni d219171e25 fix(cornerstone): read FrameOfReferenceUID from display set in viewport service (#5950) 2026-04-08 11:54:05 -04:00
ohif-bot 6cbad4fab7 chore(version): Update package versions to 3.13.0-beta.53 [skip ci] 2026-04-06 16:08:24 +00:00
Ghadeer Albattarni 0e933c256e revert: rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID (#5943) 2026-04-06 12:03:28 -04:00
ohif-bot 74e656b174 chore(version): Update package versions to 3.13.0-beta.52 [skip ci] 2026-04-06 13:27:41 +00:00
Pedro Köhler 68b10d365a fix(defaultRouteInit): pass sorted display sets to hanging protocol for deterministic viewport order (#5933)
fix: pass sorted display sets to hanging protocol for deterministic viewport order

The `applyHangingProtocol` function already sorts display sets by modality
priority and series number into `sortedDisplaySets`, but the unsorted
`displaySets` array was being passed to `hangingProtocolService.run()`.

This caused non-deterministic viewport ordering across page loads because
`displaySetService.getActiveDisplaySets()` returns display sets in creation
order, which depends on asynchronous network responses.

Made-with: Cursor
2026-04-06 09:23:37 -04:00
Dan RukasandJoe Boccanfuso 91a8715795 feat(component): Adds SmartScrollbar to ui-next - OHIF-2558 (#5924)
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2026-04-06 09:21:13 -04:00
ohif-bot fdf26aa8b9 chore(version): Update package versions to 3.13.0-beta.51 [skip ci] 2026-04-03 18:22:25 +00:00
Joe Boccanfuso 84ddf78c87 fix(security): Update yarn.lock that was missed in PR #5936. (#5940) 2026-04-03 14:17:40 -04:00
ohif-bot d7ab507f5b chore(version): Update package versions to 3.13.0-beta.50 [skip ci] 2026-04-02 22:10:46 +00:00
Joe Boccanfuso 5358a3985a fix(security): update dependencies to fix security vulnerabilities (#5936) 2026-04-02 18:06:19 -04:00
ohif-bot d670fb168c chore(version): Update package versions to 3.13.0-beta.49 [skip ci] 2026-04-02 12:57:23 +00:00
Ghadeer Albattarni b9029ef6f8 fix(segmentation): restrict overlay segmentation menu to same frame of reference as viewport background display set (#5900)
- Add FrameOfReferenceUID to SEG and RTSTRUCT displaySet in SOP Class Handlers so the FOR is available for filtering
- Sync optimisticOverlayDisplaySets when background display set changes so the overlay menu reflects the correct state after a background switch
- Add FOR matching guard to the hydrate segmentation synchronizer to prevent the hydration synchronizer from blindly mirroring segmentations from a source viewport to a target viewport if their primary Frames of Reference do not align.
- fix segmentation overlay order reversal on viewport re-render
2026-04-02 08:52:23 -04:00
ohif-bot a875d7de1f chore(version): Update package versions to 3.13.0-beta.48 [skip ci] 2026-04-01 20:57:11 +00:00
Ghadeer Albattarni 7929f0898f fix(measurement): Restore viewport interactivity when deleting in-progress Spline or Livewire measurement (#5905) 2026-04-01 16:52:14 -04:00
ohif-bot f999f3eab5 chore(version): Update package versions to 3.13.0-beta.47 [skip ci] 2026-03-28 23:24:57 +00:00
Ghadeer Albattarni e66fb5a2b8 fix: prevent black viewport when navigating series with client-created segmentation (#5919) 2026-03-28 19:20:49 -04:00
ohif-bot efac34dfe5 chore(version): Update package versions to 3.13.0-beta.46 [skip ci] 2026-03-27 20:41:39 +00:00
Ghadeer Albattarni 2a728b244c refactor(tests): update viewport page object usage to async and update all effected tests (#5927) 2026-03-27 16:37:42 -04:00
ohif-bot 485f0d75d9 chore(version): Update package versions to 3.13.0-beta.45 [skip ci] 2026-03-27 17:18:56 +00:00
Pedro Köhler d7dd12ad67 chore(refactor): use public appConfig getter instead of private _appConfig field (#5923) 2026-03-27 13:14:13 -04:00
ohif-bot 9718ecc407 chore(version): Update package versions to 3.13.0-beta.44 [skip ci] 2026-03-27 13:59:09 +00:00
diattamo fe762b68e8 chore(tests): contour segment interactions e2e tests - rename and togglevisibility (#5891) 2026-03-27 09:54:36 -04:00
ohif-bot 87eeefdf1b chore(version): Update package versions to 3.13.0-beta.43 [skip ci] 2026-03-17 13:03:35 +00:00
1d4802c2a3 fix(SR): Added support for spline and live wire SR items. (#5870)
* fix(SR): Added support for spline and live wire SR items.

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add a script to checkout a worktree for test builds

* fix: Allow download for testing sr validator

* Remove script that wasn't intended to be included

* Bump CS3D version.

* PR comments - simplify code and use single codepath for download

* Allow both download and save buttons for SEG and RTSTRUCT

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2026-03-17 08:59:47 -04:00
ohif-bot 69d8ad63f3 chore(version): Update package versions to 3.13.0-beta.42 [skip ci] 2026-03-17 11:55:55 +00:00
Bill Wallace 1df671e9ab feat: Add combined build (#5895)
* Add combined build

* Link script location update

* Security and validation fixes

* Allow specifying target path in PR description

* fix: Version match

* Fix build detection issue

* fix: Playwright deploy

* Separate out the branch merge guard

* Update docs and link info

* test: Update the layout change to wait for network idle

* Move audit late so the rest of the build can be worked on

* Add text with network check to ensure we see this change is updated

* Attempt to fix the mpr loading on ohif-downstream

* PR review comments

* Update docs

* Update to CS3D 4.20.0

* PR comments

* Add log on ohif-integration builds

* Update build test

* Removed unused space to kickoff build
2026-03-17 07:52:17 -04:00
ohif-bot 15ef4c2e0f chore(version): Update package versions to 3.13.0-beta.41 [skip ci] 2026-03-16 13:40:10 +00:00
Ghadeer AlbattarniandBill Wallace 7a38903b19 fix(sr-hydration): enable hydration and arrow navigation for 3D SR measurements (#5887)
Joe is away, so approving based on the code having the requested change, and otherwise looking good/passing tests.

* fix(sr-hydration): enable hydration and arrows navigation for 3D SR measurements

* test: add automated test for SR measurement navigation with arrows after hydration

* add cross-study warning in the 3D branch

* test: address reviewer feedback for the test

* fix: support 3D and 2D annotations for SR hydration

* test: improve navigation to first image

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2026-03-16 09:36:56 -04:00
ohif-bot 40a472a9b1 chore(version): Update package versions to 3.13.0-beta.40 [skip ci] 2026-03-13 19:28:45 +00:00
Joe Boccanfuso d8d376edaf fix(security): Bump flattened version to address CVE-2026-32141. (#5897) 2026-03-13 15:25:21 -04:00
ohif-bot d0bab783c7 chore(version): Update package versions to 3.13.0-beta.39 [skip ci] 2026-03-13 14:15:45 +00:00
Joe BoccanfusoandBill Wallace fe1ecfe0cc fix(window level): The window level value is not displayed by default on all the viewports when selecting common/custom layout and TMTV. (#5865)
* fix(window level): Set up listener for viewport availability such that the initial window level can be read and displayed.

* PR feedback.

* PR feedback.

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2026-03-13 10:12:07 -04:00
ohif-bot 54d9ea39bf chore(version): Update package versions to 3.13.0-beta.38 [skip ci] 2026-03-12 22:42:59 +00:00
Joe Boccanfuso 7b5d0ce40e fix(segmentation): Display "No description S:{series number} {modality}" for segmentations with no label. (#5874)
* Bump CS3D dependency to get the fallbackLabel field additions.
2026-03-12 18:39:14 -04:00
ohif-bot 15f14e34e4 chore(version): Update package versions to 3.13.0-beta.37 [skip ci] 2026-03-12 00:56:47 +00:00
Joe Boccanfuso d015b2e32a fix(security): Bump tar version to address CVE-2026-31802. (#5893) 2026-03-11 20:53:17 -04:00
ohif-bot 197b80eefd chore(version): Update package versions to 3.13.0-beta.36 [skip ci] 2026-03-11 18:31:47 +00:00
Bill Wallace b83e978df8 fix: Modalities in study list should select starts with as primary (#5886) 2026-03-11 14:28:20 -04:00
ohif-bot 4c2bd8d5ed chore(version): Update package versions to 3.13.0-beta.35 [skip ci] 2026-03-09 19:47:36 +00:00
Joe Boccanfuso 889026f8a9 fix(Threshold tool): Threshold tool no longer becomes deselected when the Dynamic option is selected (#5884)
fix(Threshold tool): Added 'ThresholdCircularBrushDynamic' to the toolNames array so the evaluator correctly recognizes it as an active state for the Threshold button when Dynamic mode is selected.
2026-03-09 15:43:56 -04:00
ohif-bot 1aaf569825 chore(version): Update package versions to 3.13.0-beta.34 [skip ci] 2026-03-09 13:11:54 +00:00
Ghadeer Albattarni 6f773f9972 fix(seg hydration): auto-hydrate RT struct on second load with disableConfirmationPrompts (#5875) 2026-03-09 09:08:21 -04:00
ohif-bot 0f822aaf48 chore(version): Update package versions to 3.13.0-beta.33 [skip ci] 2026-03-05 20:12:21 +00:00
Joe Boccanfuso 61a1fccd7d fix(security): Bump svgo and tar to fix vulnerabilities. (#5877) 2026-03-05 15:09:05 -05:00
ohif-bot c842642341 chore(version): Update package versions to 3.13.0-beta.32 [skip ci] 2026-03-05 18:00:06 +00:00
TFRadicalImagingandBill Wallace 70f76aeba1 feat(ecg): add DICOM ECG waveform extension (#5856)
* feat(ecg): add DICOM ECG waveform extension

Introduce @ohif/extension-dicom-ecg for rendering DICOM waveform (ECG)
data. Register the extension in the basic mode and pluginConfig.json,
and remove ECG from NON_IMAGE_MODALITIES so waveform display sets are
handled by the new viewport.

* refactor(ecg): move ECG support into cornerstone extension per review feedback

- Remove standalone dicom-ecg extension; fold all ECG functionality into
  the cornerstone extension as requested by reviewer
- Add ECG SOP class handler (DicomEcgSopClassHandler) to the cornerstone
  extension getSopClassHandlerModule, registering ECG waveform metadata
  via genericMetadataProvider on display set creation
- Move ECG helpers (buildEcgModule, decodeInt16Multiplex, base64ToArrayBuffer)
  into extensions/cornerstone/src/utils/ecgMetadata.ts
- Handle ECGViewport in CornerstoneViewportService._setDisplaySets by
  detecting ECGViewport instanceof and calling setEcg(imageId) directly,
  so OHIFCornerstoneViewport can be used without a custom ECG viewport component
- Add ECG support to getCornerstoneViewportType utility
- Update basic mode to reference the cornerstone extension's ECG SOP handler
  and use the base cornerstone viewport for ECG display sets
- Migrate ecgMetadata and getCornerstoneViewportType tests

* chore: revert bun.lock to upstream origin/master

---------

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2026-03-05 12:56:52 -05:00
ohif-bot 30bb5d0652 chore(version): Update package versions to 3.13.0-beta.31 [skip ci] 2026-03-04 20:32:11 +00:00
Ghadeer Albattarni 2358a73c3c fix(microscopy): rename measurement in microscopy mode (#5866) 2026-03-04 15:28:41 -05:00
ohif-bot df22109013 chore(version): Update package versions to 3.13.0-beta.30 [skip ci] 2026-03-04 20:25:47 +00:00
Ghadeer Albattarni 7e10830d58 fix(segmentation): segment bidirectional tool error and show toast notification when no segment is drawn (#5861) 2026-03-04 15:22:10 -05:00
ohif-bot 6db0bc2d4c chore(version): Update package versions to 3.13.0-beta.29 [skip ci] 2026-03-04 17:27:01 +00:00
Joe Boccanfuso be8e266a80 fix(security): Address various security vulnerabilities. (#5869) 2026-03-04 12:23:49 -05:00
ohif-bot b3e5f64694 chore(version): Update package versions to 3.13.0-beta.28 [skip ci] 2026-03-03 21:56:46 +00:00
diattamoandgreptile-apps[bot] f2ed4c9e6f test(ContourSegNavigation): Add e2e tests for contour segmentation navigation (#5834)
---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-03 16:52:03 -05:00
ohif-bot b6d362f375 chore(version): Update package versions to 3.13.0-beta.27 [skip ci] 2026-03-02 13:16:59 +00:00
Ghadeer Albattarni 92011c587f chore(test): add FreehandROI test that MEASUREMENT_ADDED does not fire when clicking annotation text (#5846) 2026-03-02 08:12:28 -05:00
ohif-bot c9dea4e6cc chore(version): Update package versions to 3.13.0-beta.26 [skip ci] 2026-02-28 09:54:42 +00:00
Silas Shellenbargerandsilasshellenbarger 6a3caa1ac9 fix capture not maintaining flip/rotate state (#5843)
* fix capture not maintaining flip/rotate state

* fix capture not maintaining flip/rotate state

---------

Co-authored-by: silasshellenbarger <sdshe@Silas-Computer.localdomain>
2026-02-28 04:50:35 -05:00
ohif-bot 2b486f16ed chore(version): Update package versions to 3.13.0-beta.25 [skip ci] 2026-02-26 14:54:56 +00:00
Bill WallaceandJoe Boccanfuso 5370b9382e fix: Update CS3D 4.18.2 (#5837)
* Update CS3D 4.17.4

* fix: Update to cs3d 4.18.2

* audit issues

* audit fixes

* fix: redos issue

* Simplify dependencies to fix audit alerts.

---------

Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2026-02-26 09:49:44 -05:00
ohif-bot 67f9de4ab6 chore(version): Update package versions to 3.13.0-beta.24 [skip ci] 2026-02-24 13:57:26 +00:00
Ghadeer Albattarni d11d6d401b fix: prevent viewer crash when opening DICOM Tag Browser from empty viewport (#5827) 2026-02-24 08:53:05 -05:00
Ghadeer Albattarni a8f709982f fix(ui-next): Add validation for opacity and border inputs in the segmentation panel - OHIF-2332 (#5819) 2026-02-24 08:50:07 -05:00
ohif-bot 9e4c0bb7a2 chore(version): Update package versions to 3.13.0-beta.23 [skip ci] 2026-02-23 22:29:33 +00:00
eeca1f7a7b fix: palette color 8 encoded 16 (#5823)
* fix: Detect 16 bit when 8 declared

* Update bun lock with dcmjs changes

* PR comments

* Update package versions

* fix: Palette color lookup table data for dcmjs parsed data

* fix: Published version should match (#5813)

* fix: Published version should match

* Move version number update to package.json

* fix: Netlify version number update

* Try updating platform app vesion too

* PR comments

---------

Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>

* Update latest cs3d

* formatting

* fix: Use 64k instead of 0 for number of entries

* fix: Revert CS3D version to see test issues

* Updated screenshots for ArrowAnnotate.spec.ts.

* Update CS3D version

* fix: Update cs3d 4.17.4

* Update bun lock

* Update bun

* Downgrade to CS3D 4.17.2 to try to release

---------

Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2026-02-23 17:24:33 -05:00
ohif-bot 279ae0dfe0 chore(version): Update package versions to 3.13.0-beta.22 [skip ci] 2026-02-23 21:42:30 +00:00
Joe Boccanfuso 928d592333 chore(security): Updates for running bun audit during CI and dependabot PR version update settings (#5835)
* chore(security): Update circleci config to only run bun audit for lockfile changes.

* Switched to eslint 9.39.3 for compatibility with .eslintrc.json.

* Disabled all dependabot pull requests for bun and npm version updates.
2026-02-23 16:37:56 -05:00
ohif-bot 3330d2b52b chore(version): Update package versions to 3.13.0-beta.21 [skip ci] 2026-02-23 14:19:07 +00:00
arul-trenser b5573caaa6 fix(RTStruct): RTSTRUCT contours rendered on first slice for multi-frame images without IPP (#5811)
Fix RTSTRUCT contours rendered on first slice for multi-frame images without IPP
2026-02-23 09:14:34 -05:00
ohif-bot cb4397fe31 chore(version): Update package versions to 3.13.0-beta.20 [skip ci] 2026-02-21 23:19:22 +00:00
Bill WallaceandJoe Boccanfuso d359a3b784 fix: Published version should match (#5813)
* fix: Published version should match

* Move version number update to package.json

* fix: Netlify version number update

* Try updating platform app vesion too

* PR comments

---------

Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
2026-02-21 18:15:10 -05:00
ohif-bot ce9f159c21 chore(version): Update package versions to 3.13.0-beta.19 [skip ci] 2026-02-20 22:29:35 +00:00
Joe Boccanfuso 27229609cd fix(security): CVE-2026-27212 patched. Various dependency updates as a result of CVE-2026-26996. (#5830)
fix(security):  CVE-2026-27212 patched.
Various dependency updates as a result of CVE-2026-26996.
Ultimately CVE-2026-26996 was ignored because it is only exposed in itk-wasm via CLI and OHIF's other use of minimatch is limited to build/dev environments.
2026-02-20 17:25:06 -05:00
ohif-bot 83e687fb02 chore(version): Update package versions to 3.13.0-beta.18 [skip ci] 2026-02-19 01:36:02 +00:00
b35354120a fix(segmentation overlay): update viewport ds list upon seg delete - OHIF-2425 (#5729)
- add segmentationExists check to getSopClassHandlerModule
- now firing SEGMENTATION_REMOVED and SEGMENTATION_REPRESENTATION_REMOVED events
- centralized segmentation removal in a listener
- when a segmentation is deleted (completely), remove it from all viewports it overlays
- when a segmentation is removed from a viewport, remove it as overlay from the viewport

---------

Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2026-02-18 20:31:43 -05:00
ohif-bot c75cbc2a89 chore(version): Update package versions to 3.13.0-beta.17 [skip ci] 2026-02-19 00:08:20 +00:00
Bill WallaceandDan Rukas 53c67f3612 fix: Add copy to clipboard option for capture (#5720)
* Add copy to clipboard option for capture

* PR comments

* Added toast feedback and updated labels

---------

Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2026-02-18 19:03:17 -05:00
ohif-bot c85ca0e177 chore(version): Update package versions to 3.13.0-beta.16 [skip ci] 2026-02-18 21:51:03 +00:00
Joe Boccanfuso 3d59c0d9d3 fix(security): Bump tar to 7.5.9 and lerna to 9.0.4 to fix CVE-2026-26960. (#5824)
* fix(security): Bump tar to 7.5.9 and lerna to 9.0.4 to fix CVE-2026-26960.
Bump sharp to 0.34.5 to fix tar-fs vulnerabilities.

* Update node version to 20.19.0 in circleci config. Needed for lerna and cypress tests.

* Now installing bun for cypress tests in circleci config.

* Use node version 20.19.0 in netlify config.
2026-02-18 16:46:02 -05:00
446 changed files with 32737 additions and 4916 deletions

No files matched your search

+69 -45
View File
@@ -6,7 +6,7 @@ orbs:
defaults: &defaults
docker:
- image: cimg/node:20.18.1
- image: cimg/node:20.19.0
environment:
TERM: xterm
QUICK_BUILD: true
@@ -112,50 +112,6 @@ jobs:
- checkout
- attach_workspace:
at: ~/repo
# SECURITY AUDIT
- run:
name: 'Security Audit - High Risk Vulnerabilities'
command: |
echo "🔍 Running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
# Define ignored vulnerabilities with comments
IGNORED_VULNS=(
"GHSA-5j98-mcp5-4vw2" # CVE-2025-64756 - glob is not used on the command line
"GHSA-8qq5-rm4j-mr97" # CVE-2026-23745 - limited to build/dev environments
"GHSA-r6q2-hw4h-h46w" # CVE-2026-23950 - limited to build/dev environments
"GHSA-34x7-hfp2-rc4v" # CVE-2026-24842 - limited to build/dev environments
)
# Build ignore flags
IGNORE_FLAGS=""
for vuln in "${IGNORED_VULNS[@]}"; do
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=$vuln"
done
if bun audit $IGNORE_FLAGS --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
else
echo ""
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
echo "======================================"
echo ""
echo "🔧 To fix these issues:"
echo " 1. Run: bun audit"
echo " 2. Review the vulnerability details"
echo " 3. Update affected packages to secure versions"
echo " 4. Test your changes"
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit $IGNORE_FLAGS --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
fi
- run:
name: Install Dependencies
command: bun install --frozen-lockfile
@@ -374,6 +330,7 @@ jobs:
resource_class: large
parallelism: 8
steps:
- install_bun
- run:
name: Install System Dependencies
command: |
@@ -393,6 +350,69 @@ jobs:
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
start-command: yarn run test:data && yarn run test:e2e:serve
SECURITY_AUDIT:
<<: *defaults
resource_class: large
steps:
- install_bun
- checkout
- run:
name: 'Security Audit - High Risk Vulnerabilities'
command: |
git fetch origin master 2>/dev/null || true
BASE_REF=$(git merge-base HEAD origin/master 2>/dev/null)
if [[ -z "$BASE_REF" ]]; then
echo "Could not determine base ref (e.g. shallow clone or no origin/master), skipping security audit."
exit 0
fi
CHANGED_FILES=$(git diff --name-only origin/master...HEAD 2>/dev/null || echo "")
if ! echo "$CHANGED_FILES" | grep -qx 'bun.lock'; then
echo "⏭️ bun.lock unchanged - skipping security audit."
exit 0
fi
echo "🔍 bun.lock changed - running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
# Define ignored vulnerabilities with comments
IGNORED_VULNS=(
"GHSA-3ppc-4f35-3m26" # CVE-2026-26996 - minimatch via itk-wasm and glob is safe because it does NOT use the CLI
# CVE-2026-26996 - minimatch via other packages are strictly for building and CI/CD purposes; no user supplied expressions are passed to minimatch
"GHSA-7r86-cg39-jmmj" # CVE-2026-27903 - minimatch same as above
"GHSA-23c5-xmqv-rm74" # CVE-2026-27904 - minimatch same as above
"GHSA-c2c7-rcm5-vvqj" # CVE-2026-33671 - picomatch is generally used for development and CI/CD purposes
)
# Build ignore flags
IGNORE_FLAGS=""
for vuln in "${IGNORED_VULNS[@]}"; do
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=$vuln"
done
if bun audit $IGNORE_FLAGS --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
exit 0
else
echo ""
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
echo "======================================"
echo ""
echo "🔧 To fix these issues:"
echo " 1. Run: bun audit"
echo " 2. Review the vulnerability details"
echo " 3. Update affected packages to secure versions"
echo " 4. Test your changes"
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit $IGNORE_FLAGS --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
fi
DOCKER_MULTIARCH_MANIFEST:
<<: *defaults
resource_class: large
@@ -482,6 +502,10 @@ workflows:
- CYPRESS:
name: 'Cypress Tests'
context: cypress
- SECURITY_AUDIT:
filters:
branches:
ignore: master
# viewer-dev.ohif.org
DEPLOY_MASTER:
+4
View File
@@ -2,6 +2,8 @@ version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: 'bun'
# Disable all pull requests for bun version updates.
open-pull-requests-limit: 0
directory: '/'
schedule:
interval: 'daily'
@@ -10,6 +12,8 @@ updates:
prefix: 'chore'
include: 'scope'
- package-ecosystem: 'npm'
# Disable all pull requests for npm version updates.
open-pull-requests-limit: 0
directory: '/'
schedule:
interval: 'daily'
+152 -1
View File
@@ -2,6 +2,19 @@ name: Playwright Tests
on:
pull_request:
branches: [master, release/*]
workflow_dispatch:
inputs:
cs3d_ref:
description: >-
CS3D branch (e.g. main, origin:feat/foo) or version (e.g. 4.18.2, 4.19+, 4.x). Only used
when ohif-integration label is present or via workflow_dispatch.
required: false
default: '4.19+'
permissions:
contents: read
pull-requests: read
issues: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -9,7 +22,7 @@ concurrency:
jobs:
playwright-tests:
timeout-minutes: 60
timeout-minutes: 120
runs-on: self-hosted
strategy:
fail-fast: false
@@ -25,14 +38,85 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm install -g yarn@1.22.22
# ── CS3D integration: detect label and ref type ──────────────────────
- name: Check for CS3D integration label
id: cs3d-check
run: bash .scripts/ci/cs3d-check-integration.sh
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
CS3D_REF_INPUT: ${{ github.event.inputs.cs3d_ref || '4.19+' }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Detect CS3D ref type
id: cs3d-ref
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
REF="${CS3D_REF}"
if [[ "$REF" =~ ^[0-9]+\.[0-9x]+\+?(\.[0-9x]+)?(-[a-zA-Z0-9._]+)?$ ]]; then
echo "type=version" >> "$GITHUB_OUTPUT"
RESOLVED=$(node .scripts/cs3d-resolve-version.mjs "$REF")
echo "version=$RESOLVED" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D version: $REF -> $RESOLVED"
else
echo "type=branch" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D branch: $REF"
fi
env:
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
# ── CS3D branch path: clone and build before OHIF install ───────────
- name: Clone CS3D
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
run: |
REF="${CS3D_REF}"
if [[ "$REF" == *:* ]]; then
REPO="https://github.com/${REF%%:*}/cornerstone3D.git"
BRANCH="${REF#*:}"
else
REPO="https://github.com/cornerstonejs/cornerstone3D.git"
BRANCH="$REF"
fi
echo "::notice::Cloning CS3D from $REPO branch $BRANCH"
git clone --depth 1 --branch "$BRANCH" "$REPO" libs/@cornerstonejs
env:
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
- name: Install & Build CS3D
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
working-directory: libs/@cornerstonejs
run: bun install --frozen-lockfile && bun run build:esm
# ── Common: install OHIF dependencies ───────────────────────────────
- name: Install dependencies
run: bun install --frozen-lockfile
# ── CS3D branch path: link packages after OHIF install ──────────────
- name: Link CS3D packages
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
working-directory: libs/@cornerstonejs
run: node scripts/link-ohif-cornerstone-node-modules.mjs "$GITHUB_WORKSPACE"
# ── CS3D version path: update versions after OHIF install ───────────
- name: Set CS3D version
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'version'
run: |
node .scripts/cs3d-set-version.mjs "${CS3D_VERSION}"
bun install --config=./bunfig.update-lockfile.toml
env:
CS3D_VERSION: ${{ steps.cs3d-ref.outputs.version }}
# ── Common: run tests ───────────────────────────────────────────────
- name: Install Playwright browsers
run: npx playwright install
- name: Run Playwright tests
run: |
export NODE_OPTIONS="--max_old_space_size=10192"
bun run test:e2e:coverage
# ── Common: collect test results and coverage ───────────────────────
- name: Create directory of test results
if: ${{ !cancelled() }}
run: |
@@ -56,3 +140,70 @@ jobs:
name: coverage-report-pr
path: coverage
retention-days: 3
# ── CS3D: build and deploy preview to Netlify ───────────────────────
- name: Log build context (OHIF/CS3D branch and version for build diagnosis)
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
if [[ "$CS3D_REF_TYPE" == "branch" ]]; then
echo "::notice::Build type: ohif-downstream | OHIF: ${{ github.repository }}@${{ github.ref }} (${{ github.sha }}) | CS3D: branch ${{ steps.cs3d-check.outputs.cs3d_ref }}"
else
echo "::notice::Build type: ohif-upstream | OHIF: ${{ github.repository }}@${{ github.ref }} (${{ github.sha }}) | CS3D: version ${{ steps.cs3d-ref.outputs.version }}"
fi
node .scripts/log-build-context.mjs
env:
BUILD_TYPE: ${{ steps.cs3d-ref.outputs.type == 'branch' && 'ohif-downstream' || 'ohif-upstream' }}
CS3D_REF_TYPE: ${{ steps.cs3d-ref.outputs.type }}
- name: Build OHIF viewer (CS3D preview)
if: steps.cs3d-check.outputs.enabled == 'true'
run: bun run build:ci
- name: Deploy CS3D preview to Netlify
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
RESULT=$(npx netlify-cli deploy --dir=platform/app/dist --alias="cs3d-pr-${PR_NUM}" --json --filter=@ohif/app) || {
echo "::error::Netlify deploy command failed"
exit 1
}
URL=$(echo "$RESULT" | jq -r '.deploy_url')
if [[ -z "$URL" || "$URL" == "null" ]]; then
echo "::error::Netlify deploy did not return a valid URL"
echo "$RESULT"
exit 1
fi
echo "::notice::CS3D preview deployed: $URL"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
PR_NUM: ${{ github.event.pull_request.number || 'manual' }}
# ── CS3D: log results ───────────────────────────────────────────────
- name: Log CS3D build used
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
if [[ "$CS3D_REF_TYPE" == "branch" ]]; then
echo "::notice::CS3D integration PASSED with branch ${CS3D_REF} (linked from libs/@cornerstonejs)"
else
echo "::notice::CS3D integration PASSED with @cornerstonejs/*@${CS3D_VERSION}"
fi
env:
CS3D_REF_TYPE: ${{ steps.cs3d-ref.outputs.type }}
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
CS3D_VERSION: ${{ steps.cs3d-ref.outputs.version }}
# ── Separate job: block merge when using a CS3D branch ─────────────
cs3d-branch-merge-guard:
name: "CS3D Branch Merge Guard"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Check for CS3D branch usage
run: bash .scripts/ci/cs3d-branch-merge-guard.sh
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
CS3D_REF_INPUT: ${{ github.event.inputs.cs3d_ref || '4.19+' }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
+10
View File
@@ -44,6 +44,7 @@ videos/
# autogenerated files
platform/app/src/pluginImports.js
CLAUDE.md
/Viewers.iml
platform/app/.recipes/Nginx-Dcm4Chee/logs/*
platform/app/.recipes/OpenResty-Orthanc/logs/*
@@ -61,5 +62,14 @@ tests/playwright-report/
**/.claude/settings.local.json
# cornerstone3D local linking
libs/
# Backup files
*~
# cornerstone3D local linking
libs/
link-cs3d.js
unlink-cs3d.js
auth.json
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# CS3D branch merge guard: blocks merge when tests ran against a CS3D branch (not a version).
# Exits 0 when merge is allowed or guard is skipped; exits 1 when merge must be blocked.
#
# Required env: GH_TOKEN, EVENT_NAME, REPO, PR_NUMBER
# Optional env: CS3D_REF_INPUT (for workflow_dispatch, default 4.19+)
set -e
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "::notice::workflow_dispatch — no merge to block, skipping guard."
exit 0
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
LABELS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" --jq '.[].name')
if echo "$LABELS" | grep -q "ohif-integration"; then
ENABLED=true
CS3D_REF=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.body' \
| sed -n 's/^[[:space:]]*CS3D_REF:[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
if [[ -z "$CS3D_REF" ]]; then
CS3D_REF="4.19+"
fi
else
ENABLED=false
fi
else
ENABLED=false
fi
if [[ "$ENABLED" != "true" ]]; then
echo "::notice::No ohif-integration label — skipping merge guard."
exit 0
fi
# Check if the ref is a branch (not a version)
if [[ "$CS3D_REF" =~ ^[0-9]+\.[0-9x]+\+?(\.[0-9x]+)?(-[a-zA-Z0-9._]+)?$ ]]; then
echo "::notice::CS3D ref '$CS3D_REF' is a version — merge allowed."
exit 0
fi
echo "::error::Tests ran against CS3D branch '${CS3D_REF}' — this build cannot be merged."
echo "::error::Re-run with a published CS3D version (e.g. 4.19+) before merging."
exit 1
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# CS3D integration check: detects ohif-integration label and parses CS3D_REF.
# Writes to GITHUB_OUTPUT: enabled (true|false), cs3d_ref (when enabled).
#
# Required env: GH_TOKEN, EVENT_NAME, REPO, PR_NUMBER, GITHUB_OUTPUT
# Optional env: CS3D_REF_INPUT (for workflow_dispatch, default 4.19+)
set -e
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
echo "cs3d_ref=${CS3D_REF_INPUT:-4.19+}" >> "$GITHUB_OUTPUT"
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
LABELS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" --jq '.[].name')
if echo "$LABELS" | grep -q "ohif-integration"; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
REF=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.body' \
| sed -n 's/^[[:space:]]*CS3D_REF:[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
if [[ -z "$REF" ]]; then
REF="4.19+"
fi
echo "cs3d_ref=${REF}" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D ref from PR body: ${REF}"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
+55
View File
@@ -0,0 +1,55 @@
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
const workflowPath = path.resolve(
process.cwd(),
'libs',
'@cornerstonejs',
'.github',
'workflows',
'ohif-downstream.yml'
);
if (!fs.existsSync(workflowPath)) {
console.error(`[cs3d:check] Workflow file not found: ${workflowPath}`);
process.exit(1);
}
const workflowText = fs.readFileSync(workflowPath, 'utf8');
const ohifRefMatch = workflowText.match(/^\s*OHIF_REF:\s*["']?([^"'\r\n]+)["']?\s*$/m);
if (!ohifRefMatch) {
console.error('[cs3d:check] Could not find OHIF_REF in ohif-downstream workflow.');
process.exit(1);
}
const expectedBranch = ohifRefMatch[1].trim();
let currentBranch = '';
try {
currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
cwd: process.cwd(),
encoding: 'utf8',
}).trim();
} catch (error) {
console.error('[cs3d:check] Failed to determine current git branch.');
process.exit(1);
}
if (currentBranch !== expectedBranch) {
console.error(
`[cs3d:check] Branch mismatch: current='${currentBranch}', expected='${expectedBranch}' from ${path.relative(
process.cwd(),
workflowPath
)}`
);
process.exit(1);
}
console.log(
`[cs3d:check] OK: current branch '${currentBranch}' matches OHIF_REF in ${path.relative(
process.cwd(),
workflowPath
)}`
);
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env node
/**
* Resolves a version pattern to a concrete npm version of @cornerstonejs/core.
*
* Patterns:
* 4.18.2 -> 4.18.2 (exact, returned as-is)
* 4.18.2-beta.3 -> 4.18.2-beta.3 (exact prerelease)
* 4.x -> latest 4.* from npm
* 4.17.x -> latest 4.17.* from npm
* 4.19+ -> latest >=4.19.0 <5.0.0-0 from npm (4.19 and later, same major)
*
* Prints the resolved version to stdout.
*/
import { execSync } from 'child_process';
const pattern = process.argv[2];
if (!pattern) {
console.error('Usage: cs3d-resolve-version.mjs <pattern>');
console.error(' e.g. 4.x, 4.17.x, 4.19+, 4.18.2, 4.18.2-beta.3');
process.exit(1);
}
// Exact version (no wildcard or range) — pass through unchanged
if (!pattern.includes('x') && !pattern.endsWith('+')) {
console.log(pattern);
process.exit(0);
}
// Convert "M.m+" to npm semver range ">=M.m.0 <(M+1).0.0-0"
let npmRange = pattern;
const plusMatch = pattern.match(/^(\d+)\.(\d+)\+$/);
if (plusMatch) {
const major = Number(plusMatch[1]);
const minor = Number(plusMatch[2]);
npmRange = `>=${major}.${minor}.0 <${major + 1}.0.0-0`;
}
try {
// Let npm resolve the range: @package@<range> → concrete version
const raw = execSync(`npm view @cornerstonejs/core@"${npmRange}" version --json`, {
encoding: 'utf8',
timeout: 30_000,
});
const resolved = JSON.parse(raw);
if (!resolved) {
console.error(`npm returned no version when resolving @cornerstonejs/core@${npmRange}`);
process.exit(1);
}
// npm may return an array of versions for ranges; pick the latest (last) one
const version = Array.isArray(resolved) ? resolved[resolved.length - 1] : resolved;
console.log(version);
} catch (err) {
const message =
(err && (err.stderr?.toString() || err.message || String(err))) ||
`Unknown error resolving @cornerstonejs/core@${pattern}`;
console.error(`Failed to resolve @cornerstonejs/core version for pattern "${pattern}":`);
console.error(message);
process.exit(1);
}
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env node
/**
* Updates all @cornerstonejs/* package versions across the OHIF workspace.
*
* Usage: node .scripts/cs3d-set-version.mjs <version>
*
* Only updates the 8 main CS3D packages (not codec packages):
* adapters, ai, core, dicom-image-loader, labelmap-interpolation,
* nifti-volume-loader, polymorphic-segmentation, tools
*/
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
import { resolve, dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = resolve(__dirname, '..');
const version = process.argv[2];
if (!version) {
console.error('Usage: cs3d-set-version.mjs <version>');
console.error(' e.g. 4.18.2, 4.19.0-beta.1');
process.exit(1);
}
// The 8 CS3D packages that are built from source (not codecs)
const CS3D_PACKAGES = [
'@cornerstonejs/adapters',
'@cornerstonejs/ai',
'@cornerstonejs/core',
'@cornerstonejs/dicom-image-loader',
'@cornerstonejs/labelmap-interpolation',
'@cornerstonejs/nifti-volume-loader',
'@cornerstonejs/polymorphic-segmentation',
'@cornerstonejs/tools',
];
// Read root package.json to get workspace globs
const rootPkgPath = resolve(rootDir, 'package.json');
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'));
const workspaceGlobs = rootPkg.workspaces?.packages || rootPkg.workspaces || [];
// Collect all package.json paths from workspace globs
function findWorkspacePackageJsons() {
const paths = [rootPkgPath]; // include root
for (const pattern of workspaceGlobs) {
const parts = pattern.split('/');
let searchDir = rootDir;
let hasWildcard = false;
for (const part of parts) {
if (part === '*') {
hasWildcard = true;
break;
}
searchDir = join(searchDir, part);
}
if (hasWildcard) {
try {
const entries = readdirSync(searchDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const pkgJson = join(searchDir, entry.name, 'package.json');
if (existsSync(pkgJson)) {
paths.push(pkgJson);
}
}
}
} catch {
// directory doesn't exist, skip
}
} else {
const pkgJson = join(rootDir, pattern, 'package.json');
if (existsSync(pkgJson)) {
paths.push(pkgJson);
}
}
}
return paths;
}
// Update a dependencies object, returning count of changes
function updateDeps(deps, targetVersion) {
let count = 0;
if (!deps) return count;
for (const pkg of CS3D_PACKAGES) {
if (pkg in deps && deps[pkg] !== targetVersion) {
deps[pkg] = targetVersion;
count++;
}
}
return count;
}
const pkgPaths = findWorkspacePackageJsons();
let totalChanges = 0;
for (const pkgPath of pkgPaths) {
const content = readFileSync(pkgPath, 'utf8');
const pkg = JSON.parse(content);
let changes = 0;
changes += updateDeps(pkg.dependencies, version);
changes += updateDeps(pkg.devDependencies, version);
changes += updateDeps(pkg.peerDependencies, version);
changes += updateDeps(pkg.resolutions, version);
if (changes > 0) {
// Preserve original formatting (detect indent — restrict to spaces/tabs so
// we don't accidentally capture a CRLF newline as part of the indent string)
const indent = content.match(/^([ \t]+)/m)?.[1] || ' ';
writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + '\n');
const rel = pkgPath.replace(rootDir + '/', '').replace(rootDir + '\\', '');
console.log(` Updated ${rel} (${changes} packages)`);
totalChanges += changes;
}
}
console.log(
`\nDone: ${totalChanges} version(s) updated to ${version} across ${pkgPaths.length} package files.`
);
console.log(
'This step changes package.json; the following install must not use a frozen Bun lockfile ' +
'(OHIF+CS3D combined “version” CI does: `bun install --config=./bunfig.update-lockfile.toml`). ' +
'Other installs stay frozen. Locally after this script, use that bun command and/or ' +
'`bun run install:update-lockfile` when you intend to commit lockfile updates.\n'
);
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env node
/**
* Logs build context (OHIF branch/version, CS3D source) for diagnosing build issues on GitHub.
* Run from repo root. Used by version.mjs and can be invoked from CI workflows.
*/
import { execa } from 'execa';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
function log(msg) {
console.log(`[build-context] ${msg}`);
}
async function detectCs3dSource() {
const libsCs3d = path.join(REPO_ROOT, 'libs/@cornerstonejs');
const coreInNodeModules = path.join(REPO_ROOT, 'node_modules/@cornerstonejs/core');
try {
const libsExists = await fs.access(libsCs3d).then(() => true).catch(() => false);
if (!libsExists) {
return { source: 'npm', detail: 'published @cornerstonejs packages (no libs/@cornerstonejs)' };
}
const coreStat = await fs.lstat(coreInNodeModules).catch(() => null);
const isSymlink = coreStat?.isSymbolicLink();
if (isSymlink) {
const target = await fs.readlink(coreInNodeModules);
return { source: 'integrated', detail: `linked from libs/@cornerstonejs (→ ${target})` };
}
return { source: 'npm', detail: 'published @cornerstonejs packages (libs exists but not linked)' };
} catch {
return { source: 'unknown', detail: 'could not detect' };
}
}
async function getCs3dVersion() {
try {
const corePkg = path.join(REPO_ROOT, 'node_modules/@cornerstonejs/core/package.json');
const pkg = JSON.parse(await fs.readFile(corePkg, 'utf-8'));
return pkg.version || 'unknown';
} catch {
return 'unknown';
}
}
async function getCs3dBranchFromLibs() {
try {
const libsCs3d = path.join(REPO_ROOT, 'libs/@cornerstonejs');
const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: libsCs3d,
});
return stdout;
} catch {
return null;
}
}
async function run() {
const inCI = process.env.GITHUB_ACTIONS === 'true';
const buildType = inCI ? (process.env.BUILD_TYPE || 'ohif-upstream') : 'local';
log('═══════════════════════════════════════════════════════════════');
log('Build context (for diagnosing GitHub build issues)');
log('═══════════════════════════════════════════════════════════════');
if (inCI) {
log(`Build type: ${process.env.BUILD_TYPE || 'ohif-upstream'}`);
log(`GitHub repo: ${process.env.GITHUB_REPOSITORY || 'unknown'}`);
log(`GitHub ref: ${process.env.GITHUB_REF || 'unknown'}`);
log(`GitHub SHA: ${process.env.GITHUB_SHA || 'unknown'}`);
log(`Workflow: ${process.env.GITHUB_WORKFLOW || 'unknown'}`);
}
try {
const { stdout: branch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: REPO_ROOT,
});
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
cwd: REPO_ROOT,
});
log(`OHIF branch: ${branch} (${sha})`);
} catch {
log('OHIF branch: (not a git repo or error)');
}
const cs3d = await detectCs3dSource();
log(`CS3D source: ${cs3d.source} — ${cs3d.detail}`);
if (cs3d.source === 'integrated') {
const branch = await getCs3dBranchFromLibs();
if (branch) log(`CS3D branch (libs/@cornerstonejs): ${branch}`);
} else {
const ver = await getCs3dVersion();
log(`CS3D version (@cornerstonejs/core): ${ver}`);
}
log('═══════════════════════════════════════════════════════════════');
}
run().catch((err) => {
console.error('[build-context] Error:', err.message);
process.exitCode = 1;
});
+201
View File
@@ -0,0 +1,201 @@
# AGENTS.md
This file provides guidance to AI coding agents (Claude, Codex, and other LLM tools) when working with code in this repository.
## Project Overview
This is **OHIF** v3 (Open Health Imaging Foundation) - a medical imaging viewer. It's an extensible web imaging platform.
## Development Commands
### Main Development
```bash
# Start development server for all packages
yarn dev
```
### Building
```bash
# Build all packages for production
yarn build
# Build specific packages
cd platform/app && yarn build # Main viewer app
```
## Architecture Overview
### Monorepo Structure
- **`platform/`** - Core OHIF infrastructure
- `app/` - Main viewer application (`@ohif/viewer`)
- `core/` - Core services and utilities
- `ui-next/` - Modern UI component library
- **`extensions/`** - Modular functionality plugins
- **`modes/`** - Application workflow configurations
### Key Extension Architecture
**Extension System**: Each extension exports modules (viewports, tools, panels, commands) that the app dynamically loads. Extensions are self-contained with their own webpack builds.
**Core Extensions:**
- `cornerstone/` - Medical image rendering engine
- `cornerstone-dicom-pmp/` - DICOM PMP support
- `cornerstone-dicom-seg/` - DICOM Segmentation support
- `cornerstone-dicom-sr/` - DICOM SR support
- `dicom-pdf/` - DICOM PDF support
- `dicom-video/` - DICOM Video support
- `measurement-tracking/` - Measurement tracking support
- `default/` - Standard OHIF functionality
### Service-Oriented Design (PUB-SUB)
The app uses a Services Manager pattern with these core services:
- **Display Set Service**: Manages image series organization
- **Measurement Service**: Handles annotations and measurements
- **Hanging Protocol Service**: Controls image layout and display rules
- **UI Service**: Manages panels, modals, and notifications
- **Segmentation Service**: AI/ML powered image segmentation, loading segmentations, etc.
- **Viewport Grid Service**: Manages viewport layout and display rules
- **Viewport Display Set History Service**: Manages viewport display set history
- **Viewport Dialog Service**: Manages viewport dialogs
- **Notification Service**: Manages notifications
- **Modal Service**: Manages modals
- **Dialog Service**: Manages dialogs, more general not just viewport dialogs
- **Customization Service**: Manages customization of the app
- **Toolbar Service**: Manages the toolbar, viewport action corners, tool states
- **User Authentication Service**: Manages user authentication, but used only for injecting tokens in dicomweb requests in our context
- **Panel Service**: Manages side panels
- **Cornerstone Viewport Service**: Manages the cornerstone viewport, rendering engines, presentation states, more tightly coupled to cornerstone than the other services
- **Tool Group Service**: Manages tool groups, creating and managing tool groups, etc.
- **Sync Group Service**: Manages sync groups, syncing zooming, panning, scrolling, etc.
- **Cornerstone Cache Service**: Manages the cornerstone cache, caching images, etc.
Most of the services utilize a pub sub architecture and extend the pub sub service interace at `pubSubServiceInterface.ts`
### Commands Manager
The Commands Manager tracks named commands (or functions) that are scoped to
a context. When we attempt to run a command with a given name, we look for it
in our active contexts, in the order specified.
If found, we run the command, passing in any application
or call specific data specified in the command's definition.
You can call `commandsManager.runCommand` to run a command.
### Extension Manager
Aggregates and exposes extension modules throughout the OHIF application, manages data sources, and provides a centralized registry for accessing extension functionality.
### Build System
**Yarn Workspaces**: Optimized monorepo builds with dependency caching
**Webpack 5**: Module federation for dynamic extension loading
**Plugin Import System**: Extensions auto-register via `writePluginImportsFile.js`
### Key Technologies
- **React 18 + TypeScript**: UI framework
- **Cornerstone.js**: Medical image rendering
- **DICOM**: Medical imaging standard support
- **ONNX Runtime**: AI model inference (SAM segmentation models)
- **Zustand**: State management
- **TailwindCSS**: Styling system
## Development Patterns
### Adding New Tools
1. Create tool class in `extensions/cornerstone/src/tools/`
2. Register in tool module's `toolNames.ts`
3. Add to toolbar via `getToolbarModule.tsx`
4. Add measurement mapping if needed in `measurementServiceMappings/`
### Creating Extensions
Extensions must export:
- `id.js` - Unique extension identifier
- `index.tsx` - Extension registration
- Module functions (`getToolbarModule`, `getViewportModule`, etc.)
### Viewport Customization
Custom viewports extend base Cornerstone viewport:
- Override render methods for custom overlays
- Implement measurement tracking
- Add viewport-specific tools and interactions
### Service Integration
Register services in extension's `servicesManager.registerService` and access via:
```javascript
const { MeasurementService } = servicesManager.services;
```
### Creating stores
To create a store, you can make one in your extension's `stores/` directory, and you can follow the example of an existing store such as `useLutPresentationStore.ts` or `useSynchronizersStore.ts`.
### Creating hooks
To create a hook, you can make one in your extension's `hooks/` directory, and you can follow the example of an existing hook such as `usePatientInfo.tsx`.
### Creating providers
To create a provider, you can make one in your extension's `providers/` or `contexts/` directory, and you can follow the example of an existing provider such as `ViewportGridProvider.tsx`.
### Adding new icons
To add a new icon, you can add it to the `icons/` directory, then register the icon using `import { addIcon } from '@ohif/extension-default/src/utils'`
### Creating synchronizers
You can create custom synchronizers and place them in the `synchronizers/` directory, you can follow the example of `frameViewSynchronizer.ts`
### Utilites
Any new utilites should be placed in the `utils/` directory, and you can follow the example of `formatPN.ts`
### Commands
Commands are created in the commandsModule of the extension, for example the cornerstone extension has `commandsModule.tsx`, sometimes its also named `getCommandsModule.tsx.`
### Overriding OHIF Components
To override an OHIF component, you can create a new component in your extension's `components/` directory, then import it instead of the original ui-next component.
### Mode layout
The layoutTemplate is a function that returns a layout object, you can follow the example of `longitudinal/src/index.ts`. This would be helpful when you need to override a component as you can know where to look for the original component.
### Pub Sub
Always prioritrize pub sub, by calling a services subscribe over useEffects as it's more reliable, for example
```ts
useEffect(() => {
const subscriptions = [
cornerstoneViewportService.subscribe(EVENTS.VIEWPORT_DATA_CHANGED, handleViewportDataChanged),
syncGroupService.subscribe(EVENTS.VIEWPORT_REMOVED, onHotKeyRemoval),
syncGroupService.subscribe(EVENTS.VIEWPORT_ADDED, onHotKeyAddition),
];
return () => {
subscriptions.forEach(({ unsubscribe }) => unsubscribe());
};
}, []);
```
### Never modify core architecture
Do not modify the core and always find a way to implement the solution via the extensions and modes, only modify core as a last resort if all other fail or there's an architectural constraint.
## Configuration
### Plugin Configuration
Extensions are auto-discovered via `pluginConfig.json` and dynamically imported during build.
## Medical Imaging Specifics
### DICOM Support
- Multi-format: CT, MRI, X-Ray, Mammography, Ultrasound
- SOP Class handlers for specialized DICOM types (RT, SEG, SR)
- DICOMweb protocol for web-based image retrieval
### Hanging Protocols
Define how images are arranged and displayed:
- Located in `hps/` directories
- JSON configuration with viewport rules
- Support for priors comparison and multi-monitor layouts
### Measurement Tools
- Cornerstone Tools integration for annotations
- Bidirectional measurements, polylines, annotations
- Export capabilities (DICOM SR, CSV reports)
- AI-assisted measurements via ONNX models
+663
View File
@@ -3,6 +3,669 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
### Bug Fixes
* **measurements:** read displayName in SplineROI and PlanarFreehandROI reports ([#5908](https://github.com/OHIF/Viewers/issues/5908)) ([27af682](https://github.com/OHIF/Viewers/commit/27af6821b56f2c5be3a5d4ce38d05be6fbce68f4))
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
### Bug Fixes
* **segmentation:** remove thresholdRange label in double-range tool settings ([#5931](https://github.com/OHIF/Viewers/issues/5931)) ([527cfb0](https://github.com/OHIF/Viewers/commit/527cfb0ae8c615aeaddbc7f16892ba4460a5b596))
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
### Bug Fixes
* **security:** Update various dependencies to fix security vulnerabilities. ([#6023](https://github.com/OHIF/Viewers/issues/6023)) ([55b46f3](https://github.com/OHIF/Viewers/commit/55b46f39c65a183978a44a61fb322685134f3bd6))
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
### Features
* **llm:** add instructions md file for claude and other LLM tools ([#6017](https://github.com/OHIF/Viewers/issues/6017)) ([f231eff](https://github.com/OHIF/Viewers/commit/f231eff5980495c9f3ac0bd907a03388d06c5b90))
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
### Bug Fixes
* **measurement-tracking:** restore tracked state on undo after Delete all ([#5994](https://github.com/OHIF/Viewers/issues/5994)) ([397aa4d](https://github.com/OHIF/Viewers/commit/397aa4d0e361e95dcbd83e0557a9cd84c0b8440a))
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
### Bug Fixes
* remove waitForVolumeLoad from main toolbar page object ([#6004](https://github.com/OHIF/Viewers/issues/6004)) ([37e19f7](https://github.com/OHIF/Viewers/commit/37e19f734a675a95cedc8a8acc3980816a489574))
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
### Bug Fixes
* **security:** Patch axios security vulnerabilities. ([#5998](https://github.com/OHIF/Viewers/issues/5998)) ([5e624f1](https://github.com/OHIF/Viewers/commit/5e624f13870cdb63300b82285b46d4b21b9d47c3))
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
### Bug Fixes
* **ToolGroupService:** Centralize tool binding persistence and provide API to add and remove persisted bindings. ([#5989](https://github.com/OHIF/Viewers/issues/5989)) ([979d619](https://github.com/OHIF/Viewers/commit/979d619f7ccb426beb75d59721b01c98175d5241))
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
### Features
* **ui-next:** Adds toggle state for ToolButton and Crosshair example ([#5914](https://github.com/OHIF/Viewers/issues/5914)) ([691e267](https://github.com/OHIF/Viewers/commit/691e26731f4f3b3957fe81fc051b09c972696cf6))
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
### Bug Fixes
* **config url:** Hardening fetch options. ([#5985](https://github.com/OHIF/Viewers/issues/5985)) ([468e573](https://github.com/OHIF/Viewers/commit/468e5734a9ab02516430cc0dab5d7f2106dea950))
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
### Bug Fixes
* **seg:** prevent segmentations from spreading to all viewports before hydration confirmation in 3D four-up ([#5967](https://github.com/OHIF/Viewers/issues/5967)) ([f8ccf9f](https://github.com/OHIF/Viewers/commit/f8ccf9ff2ea9ab7c38bd427514a8ae87902822a3))
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
### Bug Fixes
* **security:** refine dynamic config URL trust policy and document trusted-origin behavior ([#5973](https://github.com/OHIF/Viewers/issues/5973)) ([f3cca21](https://github.com/OHIF/Viewers/commit/f3cca21e49729fbf4bab85aa1de64b9dcfebd0ce))
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
### Bug Fixes
* **security:** Patch protobufjs for CVE-2026-41242. ([#5974](https://github.com/OHIF/Viewers/issues/5974)) ([1fc97fe](https://github.com/OHIF/Viewers/commit/1fc97fea43cc5e6689bd0076b77a278c67252af7))
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
### Features
* **slice scrollbar:** Integrate ViewportSliceProgressScrollbar with customizations and docs ([#5960](https://github.com/OHIF/Viewers/issues/5960)) ([8fc0dc1](https://github.com/OHIF/Viewers/commit/8fc0dc16e97f3262fdac902ecf2d7c20cb8f78cc))
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
### Bug Fixes
* **configuration:** Harden dynamic datasource URL trust boundaries and credential handling. ([#5963](https://github.com/OHIF/Viewers/issues/5963)) ([eede569](https://github.com/OHIF/Viewers/commit/eede569a88ed6a3177bbdea8552dfb93ca13cac5))
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
### Bug Fixes
* Enhance multiframe instance handling and improve instance valida… ([#5956](https://github.com/OHIF/Viewers/issues/5956)) ([f5a66b9](https://github.com/OHIF/Viewers/commit/f5a66b9eea236cd3291eac857208d7465516c89a))
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
### Bug Fixes
* ignore auth in git ([#5955](https://github.com/OHIF/Viewers/issues/5955)) ([961eea1](https://github.com/OHIF/Viewers/commit/961eea1c39c37a940c1707c3dfafcede5f4af994))
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
### Bug Fixes
* **cornerstone:** read FrameOfReferenceUID from display set in viewport service ([#5950](https://github.com/OHIF/Viewers/issues/5950)) ([d219171](https://github.com/OHIF/Viewers/commit/d219171e25caf5a7c575080f63819d6f4fe45016))
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
### Bug Fixes
* **defaultRouteInit:** pass sorted display sets to hanging protocol for deterministic viewport order ([#5933](https://github.com/OHIF/Viewers/issues/5933)) ([68b10d3](https://github.com/OHIF/Viewers/commit/68b10d365a72d656c83a1c61624dcbd245fff5db))
### Features
* **component:** Adds SmartScrollbar to ui-next - OHIF-2558 ([#5924](https://github.com/OHIF/Viewers/issues/5924)) ([91a8715](https://github.com/OHIF/Viewers/commit/91a8715795c1501b271f10b361382331eb836bf9))
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
### Bug Fixes
* **security:** Update yarn.lock that was missed in PR [#5936](https://github.com/OHIF/Viewers/issues/5936). ([#5940](https://github.com/OHIF/Viewers/issues/5940)) ([84ddf78](https://github.com/OHIF/Viewers/commit/84ddf78c879a7cb1aa62c3aaef4428adf67065da))
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
### Bug Fixes
* **security:** update dependencies to fix security vulnerabilities ([#5936](https://github.com/OHIF/Viewers/issues/5936)) ([5358a39](https://github.com/OHIF/Viewers/commit/5358a3985a1fa87eb1e3e7493dd61254f790b6d8))
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
### Bug Fixes
* **measurement:** Restore viewport interactivity when deleting in-progress Spline or Livewire measurement ([#5905](https://github.com/OHIF/Viewers/issues/5905)) ([7929f08](https://github.com/OHIF/Viewers/commit/7929f0898f1bf882682a912b1c32d93d0944726f))
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
### Bug Fixes
* prevent black viewport when navigating series with client-created segmentation ([#5919](https://github.com/OHIF/Viewers/issues/5919)) ([e66fb5a](https://github.com/OHIF/Viewers/commit/e66fb5a2b8c93a60ea20d315f73def7a02ea205f))
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
### Bug Fixes
* **SR:** Added support for spline and live wire SR items. ([#5870](https://github.com/OHIF/Viewers/issues/5870)) ([1d4802c](https://github.com/OHIF/Viewers/commit/1d4802c2a38dd75dca7afca87913e100453c9135))
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
### Bug Fixes
* **sr-hydration:** enable hydration and arrow navigation for 3D SR measurements ([#5887](https://github.com/OHIF/Viewers/issues/5887)) ([7a38903](https://github.com/OHIF/Viewers/commit/7a38903b1977b1cf0f3a0ba8a4a823fac5e8fdeb))
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
### Bug Fixes
* **security:** Bump flattened version to address CVE-2026-32141. ([#5897](https://github.com/OHIF/Viewers/issues/5897)) ([d8d376e](https://github.com/OHIF/Viewers/commit/d8d376edaf9c2b7af237182657e3e2203cca5abb))
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
### Bug Fixes
* **window level:** The window level value is not displayed by default on all the viewports when selecting common/custom layout and TMTV. ([#5865](https://github.com/OHIF/Viewers/issues/5865)) ([fe1ecfe](https://github.com/OHIF/Viewers/commit/fe1ecfe0cc5d1bcf82686d821f356da8936a8c4b))
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
### Bug Fixes
* **security:** Bump tar version to address CVE-2026-31802. ([#5893](https://github.com/OHIF/Viewers/issues/5893)) ([d015b2e](https://github.com/OHIF/Viewers/commit/d015b2e32a418042034c4f557c5a36499c72f702))
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
### Bug Fixes
* Modalities in study list should select starts with as primary ([#5886](https://github.com/OHIF/Viewers/issues/5886)) ([b83e978](https://github.com/OHIF/Viewers/commit/b83e978df8c9ffda3759378318267b8d0020653c))
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
### Bug Fixes
* **Threshold tool:** Threshold tool no longer becomes deselected when the Dynamic option is selected ([#5884](https://github.com/OHIF/Viewers/issues/5884)) ([889026f](https://github.com/OHIF/Viewers/commit/889026f8a9c20cd21c31b4a02128afd5f057b14b))
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
### Bug Fixes
* **seg hydration:** auto-hydrate RT struct on second load with disableConfirmationPrompts ([#5875](https://github.com/OHIF/Viewers/issues/5875)) ([6f773f9](https://github.com/OHIF/Viewers/commit/6f773f997214c388409f3af3e9fc0e5b84debc10))
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
### Bug Fixes
* **security:** Bump svgo and tar to fix vulnerabilities. ([#5877](https://github.com/OHIF/Viewers/issues/5877)) ([61a1fcc](https://github.com/OHIF/Viewers/commit/61a1fccd7d47965831a6c80357219eed979ce4d6))
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
### Features
* **ecg:** add DICOM ECG waveform extension ([#5856](https://github.com/OHIF/Viewers/issues/5856)) ([70f76ae](https://github.com/OHIF/Viewers/commit/70f76aeba1b27f8e4de5ec73a7b831428e38e3d2))
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
### Bug Fixes
* **microscopy:** rename measurement in microscopy mode ([#5866](https://github.com/OHIF/Viewers/issues/5866)) ([2358a73](https://github.com/OHIF/Viewers/commit/2358a73c3cd24953430064480fb9e66bfe36ea69))
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
### Bug Fixes
* **segmentation:** segment bidirectional tool error and show toast notification when no segment is drawn ([#5861](https://github.com/OHIF/Viewers/issues/5861)) ([7e10830](https://github.com/OHIF/Viewers/commit/7e10830d5802e22bda4df6a3f8008e9a414b4cd4))
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
### Bug Fixes
* **security:** Address various security vulnerabilities. ([#5869](https://github.com/OHIF/Viewers/issues/5869)) ([be8e266](https://github.com/OHIF/Viewers/commit/be8e266a80f13d03ce9dc4a21a0227d3e1bc36ac))
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
### Bug Fixes
* prevent viewer crash when opening DICOM Tag Browser from empty viewport ([#5827](https://github.com/OHIF/Viewers/issues/5827)) ([d11d6d4](https://github.com/OHIF/Viewers/commit/d11d6d401bc92a3870ec1089d97235b01a6b27c3))
* **ui-next:** Add validation for opacity and border inputs in the segmentation panel - OHIF-2332 ([#5819](https://github.com/OHIF/Viewers/issues/5819)) ([a8f7099](https://github.com/OHIF/Viewers/commit/a8f709982f16983a11c780e696521df5aa05b604))
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package ohif-monorepo-root
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
### Bug Fixes
* **RTStruct:** RTSTRUCT contours rendered on first slice for multi-frame images without IPP ([#5811](https://github.com/OHIF/Viewers/issues/5811)) ([b5573ca](https://github.com/OHIF/Viewers/commit/b5573caaa65d0d7ff3e83df0ec70b4aa7ea898ca))
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
### Bug Fixes
* Published version should match ([#5813](https://github.com/OHIF/Viewers/issues/5813)) ([d359a3b](https://github.com/OHIF/Viewers/commit/d359a3b784227b248c5b09548db363c7f8adbcb8))
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
### Bug Fixes
* **security:** CVE-2026-27212 patched. Various dependency updates as a result of CVE-2026-26996. ([#5830](https://github.com/OHIF/Viewers/issues/5830)) ([2722960](https://github.com/OHIF/Viewers/commit/27229609cda3162e37573418ef82ca5d4e365075))
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
### Bug Fixes
* **segmentation overlay:** update viewport ds list upon seg delete - OHIF-2425 ([#5729](https://github.com/OHIF/Viewers/issues/5729)) ([b353541](https://github.com/OHIF/Viewers/commit/b35354120a01d89b27844f967c32c052bba8b096))
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
### Bug Fixes
* Add copy to clipboard option for capture ([#5720](https://github.com/OHIF/Viewers/issues/5720)) ([53c67f3](https://github.com/OHIF/Viewers/commit/53c67f361234a0b4160c1b11fafcb143a0723336))
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
### Bug Fixes
* **security:** Bump tar to 7.5.9 and lerna to 9.0.4 to fix CVE-2026-26960. ([#5824](https://github.com/OHIF/Viewers/issues/5824)) ([3d59c0d](https://github.com/OHIF/Viewers/commit/3d59c0d9d3783336eaa8d37cfc0172818942bf00))
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
+59 -1
View File
@@ -49,6 +49,7 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-4d.webp?raw=true" alt="4D" width="350"/> | 4D | [Demo](https://viewer.ohif.org/dynamic-volume?StudyInstanceUIDs=2.25.232704420736447710317909004159492840763) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-video.webp?raw=true" alt="VIDEO" width="350"/> | Video | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=2.25.96975534054447904995905761963464388233) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/microscopy.webp?raw=true" alt="microscopy" width="350"/> | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.141277760791347900862109212450152067508) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-ecg.webp?raw=true" alt="ECG" width="350"/> | ECG Waveform | [Demo](https://viewer-dev.ohif.org/viewer?StudyInstanceUIDs=2.25.209974489360710696739324151261716440238) |
## About
@@ -104,7 +105,6 @@ For commercial support, academic collaborations, and answers to common
questions; please use [Get Support](https://ohif.org/get-support/) to contact
us.
## Developing
### Branches
@@ -168,6 +168,64 @@ yarn config set workspaces-experimental true
yarn install --frozen-lockfile
```
### Cornerstone3D Integration Testing
OHIF's Playwright end-to-end tests can run against a **CS3D branch** or a
**published CS3D version**, allowing changes that span both repositories to be
validated together before merging.
#### Setting up an integration build
1. Add the **`ohif-integration`** label to your OHIF pull request.
2. In the PR body, add a line specifying the CS3D ref:
```
CS3D_REF: feat/my-feature
```
- **Version ref** (e.g. `4.19+`, `4.18.2`) — the workflow resolves it to an
exact published version and swaps the CS3D dependency via npm.
- **Branch ref** (e.g. `main`, `cornerstonejs:feat/foo`) — the workflow
clones the branch, builds CS3D from source with `bun run build:esm`, and
symlinks the built packages into OHIF's `node_modules`.
- For forks, use the `<owner>:<branch>` format
(e.g. `myGithubUser:feat/foo`).
- If no `CS3D_REF` is specified, the default is `4.19+`.
3. The workflow can also be triggered manually via **workflow_dispatch** with a
`cs3d_ref` input.
#### What happens in CI
The [Playwright workflow](.github/workflows/playwright.yml) runs two jobs:
| Job | Purpose |
|-----|---------|
| **Playwright Tests** | Builds OHIF (with CS3D linked or version-swapped), runs the full Playwright suite, uploads test results and coverage, and deploys a Netlify preview when `ohif-integration` is active. |
| **CS3D Branch Merge Guard** | A lightweight check that **fails** when the `ohif-integration` label is present and `CS3D_REF` points to a branch (not a version). This prevents merging while still letting the Playwright tests show green so you can see whether the code actually works. |
#### Testing changes that span both repos
If a feature requires changes in both Cornerstone3D and OHIF:
1. Create your feature branch in CS3D and push it.
2. Create a matching branch in OHIF.
3. Add the `ohif-integration` label to the OHIF pull request.
4. In the PR body, add: `CS3D_REF: <your-cs3d-branch>`.
5. Playwright tests will build CS3D from source, link it, and run the full
suite. The merge guard will block merge until you switch to a published
version — but you can see the test results and the preview deploy while
iterating.
6. Once the CS3D side is merged and published, update the PR body to reference
the published version (e.g. `CS3D_REF: 4.19+`). The tests will run against
the registry version and the merge guard will pass.
#### Preview deploys
When `ohif-integration` is active, the Playwright workflow also builds the OHIF
viewer and deploys it to Netlify as a preview. This gives you a live URL to
manually test the combined CS3D + OHIF changes without running anything locally.
For details on linking CS3D locally for development, see the
[Cornerstone3D README](libs/@cornerstonejs/README.md#local-development-linking--unlinking).
## Commands
These commands are available from the root directory. Each project directory
+510
View File
@@ -3,6 +3,516 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
### Bug Fixes
* **security:** CVE-2026-27212 patched. Various dependency updates as a result of CVE-2026-26996. ([#5830](https://github.com/OHIF/Viewers/issues/5830)) ([2722960](https://github.com/OHIF/Viewers/commit/27229609cda3162e37573418ef82ca5d4e365075))
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @externals/devDependencies
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
### Bug Fixes
* **security:** Bump tar to 7.5.9 and lerna to 9.0.4 to fix CVE-2026-26960. ([#5824](https://github.com/OHIF/Viewers/issues/5824)) ([3d59c0d](https://github.com/OHIF/Viewers/commit/3d59c0d9d3783336eaa8d37cfc0172818942bf00))
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @externals/devDependencies
+9 -13
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/devDependencies",
"description": "External dev dependencies - put dev build dependencies here",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"license": "MIT",
"private": true,
"engines": {
@@ -20,7 +20,6 @@
"react-dom": "18.3.1"
},
"devDependencies": {
"@babel/eslint-parser": "7.28.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.17",
"@rsbuild/core": "1.5.1",
"@rsbuild/plugin-node-polyfill": "1.4.2",
@@ -28,30 +27,27 @@
"@svgr/webpack": "8.1.0",
"@swc/helpers": "0.5.17",
"@types/jest": "27.5.2",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"@typescript-eslint/eslint-plugin": "8.56.0",
"@typescript-eslint/parser": "8.56.0",
"autoprefixer": "10.4.21",
"babel-loader": "8.4.1",
"babel-plugin-module-resolver": "5.0.2",
"clean-webpack-plugin": "3.0.0",
"copy-webpack-plugin": "9.1.0",
"cross-env": "7.0.3",
"css-loader": "6.11.0",
"dotenv": "8.6.0",
"eslint": "8.57.1",
"eslint": "9.39.3",
"eslint-config-prettier": "7.2.0",
"eslint-config-react-app": "6.0.0",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-cypress": "2.15.2",
"eslint-plugin-flowtype": "7.0.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "5.5.1",
"eslint-plugin-promise": "5.2.0",
"eslint-plugin-promise": "7.2.1",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-tsdoc": "0.2.17",
"eslint-webpack-plugin": "2.7.0",
"execa": "8.0.1",
"extract-css-chunks-webpack-plugin": "4.10.0",
"html-webpack-plugin": "5.6.3",
@@ -60,7 +56,7 @@
"jest-canvas-mock": "2.5.2",
"jest-environment-jsdom": "29.7.0",
"jest-junit": "6.4.0",
"lerna": "7.4.2",
"lerna": "9.0.4",
"lint-staged": "9.5.0",
"mini-css-extract-plugin": "2.9.2",
"optimize-css-assets-webpack-plugin": "6.0.1",
@@ -72,7 +68,7 @@
"prettier-plugin-tailwindcss": "0.6.9",
"react-refresh": "0.14.2",
"semver": "7.7.2",
"serve": "14.2.4",
"serve": "14.2.5",
"shader-loader": "1.3.1",
"shx": "0.3.4",
"source-map-loader": "4.0.2",
+504
View File
@@ -3,6 +3,510 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-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.13.0-beta.15",
"version": "3.13.0-beta.78",
"license": "MIT",
"dependencies": {
"dicom-microscopy-viewer": "0.48.17"
+1 -1
View File
@@ -31,7 +31,7 @@
"@babel/plugin-transform-regenerator": "7.28.1",
"@babel/plugin-transform-runtime": "7.28.0",
"@babel/plugin-transform-typescript": "7.28.0",
"@babel/preset-env": "7.28.0",
"@babel/preset-env": "7.29.5",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1"
},
+1947 -985
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
cca1a8683bc7d392a3a07268f24585642d13b1cd
27af6821b56f2c5be3a5d4ce38d05be6fbce68f4
@@ -3,6 +3,528 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "DICOM Parametric Map read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/extension-cornerstone": "3.13.0-beta.15",
"@ohif/extension-default": "3.13.0-beta.15",
"@ohif/i18n": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/extension-cornerstone": "3.13.0-beta.78",
"@ohif/extension-default": "3.13.0-beta.78",
"@ohif/i18n": "3.13.0-beta.78",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.29",
"@cornerstonejs/core": "4.15.29",
"@cornerstonejs/adapters": "4.22.8",
"@cornerstonejs/core": "4.22.8",
"@kitware/vtk.js": "34.15.1"
}
}
@@ -3,6 +3,519 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
### Bug Fixes
* **segmentation overlay:** update viewport ds list upon seg delete - OHIF-2425 ([#5729](https://github.com/OHIF/Viewers/issues/5729)) ([b353541](https://github.com/OHIF/Viewers/commit/b35354120a01d89b27844f967c32c052bba8b096))
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-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.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/extension-cornerstone": "3.13.0-beta.15",
"@ohif/extension-default": "3.13.0-beta.15",
"@ohif/i18n": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/extension-cornerstone": "3.13.0-beta.78",
"@ohif/extension-default": "3.13.0-beta.78",
"@ohif/i18n": "3.13.0-beta.78",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -1,5 +1,6 @@
import { utils, Types as OhifTypes } from '@ohif/core';
import i18n from '@ohif/i18n';
import { segmentation as cstSegmentation } from '@cornerstonejs/tools';
import { SOPClassHandlerId } from './id';
import loadRTStruct from './loadRTStruct';
@@ -8,8 +9,6 @@ const { sopClassDictionary } = utils;
const sopClassUids = [sopClassDictionary.RTStructureSetStorage];
const cachedRTStructsSEG = new Set<string>();
const loadPromises = {};
function _getDisplaySetsFromSeries(
@@ -57,6 +56,7 @@ function _getDisplaySetsFromSeries(
StudyInstanceUID,
SOPClassHandlerId,
SOPClassUID,
FrameOfReferenceUID: null,
referencedImages: null,
referencedSeriesInstanceUID: null,
referencedDisplaySetInstanceUID: null,
@@ -96,6 +96,9 @@ function _getDisplaySetsFromSeries(
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
displaySet.FrameOfReferenceUID =
instance.ReferencedFrameOfReferenceSequence?.[0]?.FrameOfReferenceUID;
const { displaySetService } = servicesManager.services;
const referencedDisplaySets =
displaySetService.getDisplaySetsForReferences(referencedSeriesSequence);
@@ -114,6 +117,7 @@ function _getDisplaySetsFromSeries(
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
displaySet.FrameOfReferenceUID = addedDisplaySet.FrameOfReferenceUID;
unsubscribe();
}
}
@@ -122,6 +126,7 @@ function _getDisplaySetsFromSeries(
const [referencedDisplaySet] = referencedDisplaySets;
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
displaySet.FrameOfReferenceUID = referencedDisplaySet.FrameOfReferenceUID;
}
displaySet.load = ({ headers, createSegmentation = true }) =>
@@ -143,23 +148,13 @@ function _load(
if (
(rtDisplaySet.loading || rtDisplaySet.isLoaded) &&
loadPromises[SOPInstanceUID] &&
cachedRTStructsSEG.has(rtDisplaySet.displaySetInstanceUID)
_segmentationExists(rtDisplaySet)
) {
return loadPromises[SOPInstanceUID];
}
rtDisplaySet.loading = true;
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
(evt: { rtDisplaySet: { displaySetInstanceUID: string } }) => {
if (evt.rtDisplaySet?.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
cachedRTStructsSEG.add(rtDisplaySet.displaySetInstanceUID);
unsubscribe();
}
}
);
// We don't want to fire multiple loads, so we'll wait for the first to finish
// and also return the same promise to any other callers.
loadPromises[SOPInstanceUID] = new Promise<void>(async (resolve, reject) => {
@@ -219,6 +214,10 @@ function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
return ReferencedSeriesSequence;
}
function _segmentationExists(segDisplaySet) {
return !!cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID);
}
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
const { servicesManager, extensionManager } = params;
@@ -168,7 +168,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
return () => {
// remove the segmentation representations if seg displayset changed
segmentationService.removeSegmentationRepresentations(viewportId);
segmentationService.removeRepresentationsFromViewport(viewportId);
referencedDisplaySetRef.current = null;
toolGroupService.destroyToolGroup(toolGroupId);
};
@@ -3,6 +3,537 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
### Bug Fixes
* **SR:** Added support for spline and live wire SR items. ([#5870](https://github.com/OHIF/Viewers/issues/5870)) ([1d4802c](https://github.com/OHIF/Viewers/commit/1d4802c2a38dd75dca7afca87913e100453c9135))
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/extension-cornerstone": "3.13.0-beta.15",
"@ohif/extension-default": "3.13.0-beta.15",
"@ohif/i18n": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/extension-cornerstone": "3.13.0-beta.78",
"@ohif/extension-default": "3.13.0-beta.78",
"@ohif/i18n": "3.13.0-beta.78",
"prop-types": "15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.29",
"@cornerstonejs/core": "4.15.29",
"@cornerstonejs/adapters": "4.22.8",
"@cornerstonejs/core": "4.22.8",
"@kitware/vtk.js": "34.15.1"
}
}
@@ -2,9 +2,8 @@ import dcmjs from 'dcmjs';
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 { adaptersRT, adaptersSEG } from '@cornerstonejs/adapters';
import { createReportDialogPrompt, useUIStateStore } from '@ohif/extension-default';
import { DicomMetadataStore } from '@ohif/core';
import PROMPT_RESPONSES from '../../default/src/utils/_shared/PROMPT_RESPONSES';
@@ -29,11 +28,11 @@ const {
},
} = adaptersRT;
const { downloadDICOMData } = helpers;
const commandsModule = ({
servicesManager,
extensionManager,
commandsManager,
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
const { segmentationService, displaySetService, viewportGridService } =
servicesManager.services as AppTypes.Services;
@@ -194,8 +193,11 @@ const commandsModule = ({
const generatedSegmentation = actions.generateSegmentation({
segmentationId,
});
downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`);
const storeFn = commandsManager.runCommand('createStoreFunction', {
dataSource: 'download',
defaultFileName: `${segmentationInOHIF.label}.dcm`,
});
storeFn(generatedSegmentation.dataset);
},
/**
* Stores a segmentation based on the provided segmentationId into a specified data source.
@@ -217,11 +219,10 @@ const commandsModule = ({
}
const { label, predecessorImageId } = segmentation;
const defaultDataSource = dataSource ?? extensionManager.getActiveDataSource()[0];
const {
value: reportName,
dataSourceName: selectedDataSource,
dataSourceName,
series,
priorSeriesNumber,
action,
@@ -231,50 +232,58 @@ const commandsModule = ({
predecessorImageId,
title: 'Store Segmentation',
modality,
enableDownload: true,
});
if (action === PROMPT_RESPONSES.CREATE_REPORT) {
try {
const selectedDataSourceConfig = selectedDataSource
? extensionManager.getDataSources(selectedDataSource)[0]
: defaultDataSource;
if (action !== PROMPT_RESPONSES.CREATE_REPORT) {
return;
}
const args = {
segmentationId,
options: {
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;
const defaultFileName =
modality === 'RTSTRUCT'
? `rtss-${segmentationId}.dcm`
: `${label || 'segmentation'}.dcm`;
if (!generatedData || !generatedData.dataset) {
throw new Error('Error during segmentation generation');
}
const storeFn = commandsManager.runCommand('createStoreFunction', {
dataSource: dataSourceName,
defaultFileName,
});
const { dataset: naturalizedReport } = generatedData;
if (!storeFn) {
throw new Error(`No valid store for dataSource: ${dataSourceName}`);
}
// DCMJS assigns a dummy study id during creation, and this can cause problems, so clearing it out
if (naturalizedReport.StudyID === 'No Study ID') {
naturalizedReport.StudyID = '';
}
try {
const args = {
segmentationId,
options: {
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;
await selectedDataSourceConfig.store.dicom(naturalizedReport);
// add the information for where we stored it to the instance as well
naturalizedReport.wadoRoot = selectedDataSourceConfig.getConfig().wadoRoot;
DicomMetadataStore.addInstances([naturalizedReport], true);
return naturalizedReport;
} catch (error) {
console.debug('Error storing segmentation:', error);
throw error;
if (!generatedData?.dataset) {
throw new Error('Error during segmentation generation');
}
const { dataset: naturalizedReport } = generatedData;
// DCMJS assigns a dummy study id during creation, and this can cause problems, so clearing it out
if (naturalizedReport.StudyID === 'No Study ID') {
naturalizedReport.StudyID = '';
}
await storeFn(naturalizedReport, {});
return naturalizedReport;
} catch (error) {
console.debug('Error storing segmentation:', error);
throw error;
}
},
@@ -307,14 +316,11 @@ const commandsModule = ({
downloadRTSS: async args => {
const { dataset } = await actions.generateContour(args);
const { InstanceNumber: instanceNumber = 1, SeriesInstanceUID: seriesUID } = dataset;
try {
//Create a URL for the binary.
const filename = `rtss-${seriesUID}-${instanceNumber}.dcm`;
downloadDICOMData(dataset, filename);
} catch (e) {
console.warn(e);
}
const storeFn = commandsManager.runCommand('createStoreFunction', {
dataSource: 'download',
defaultFileName: `rtss-${seriesUID}-${instanceNumber}.dcm`,
});
await storeFn(dataset);
},
toggleActiveSegmentationUtility: ({ itemId: buttonId }) => {
@@ -30,6 +30,7 @@ function _getDisplaySetsFromSeries(
SeriesDate,
StructureSetDate,
SOPClassUID,
FrameOfReferenceUID,
wadoRoot,
wadoUri,
wadoUriRoot,
@@ -49,6 +50,7 @@ function _getDisplaySetsFromSeries(
StudyInstanceUID,
SOPClassHandlerId,
SOPClassUID,
FrameOfReferenceUID,
referencedImages: null,
referencedSeriesInstanceUID: null,
referencedDisplaySetInstanceUID: null,
@@ -104,6 +106,7 @@ function _getDisplaySetsFromSeries(
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
displaySet.FrameOfReferenceUID = addedDisplaySet.FrameOfReferenceUID;
unsubscribe();
}
}
@@ -111,6 +114,7 @@ function _getDisplaySetsFromSeries(
} else {
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
displaySet.FrameOfReferenceUID = referencedDisplaySet.FrameOfReferenceUID;
}
displaySet.load = async ({ headers }) =>
@@ -3,6 +3,540 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
### Bug Fixes
* **SR:** Added support for spline and live wire SR items. ([#5870](https://github.com/OHIF/Viewers/issues/5870)) ([1d4802c](https://github.com/OHIF/Viewers/commit/1d4802c2a38dd75dca7afca87913e100453c9135))
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
### Bug Fixes
* **sr-hydration:** enable hydration and arrow navigation for 3D SR measurements ([#5887](https://github.com/OHIF/Viewers/issues/5887)) ([7a38903](https://github.com/OHIF/Viewers/commit/7a38903b1977b1cf0f3a0ba8a4a823fac5e8fdeb))
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -34,10 +34,10 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/extension-cornerstone": "3.13.0-beta.15",
"@ohif/extension-measurement-tracking": "3.13.0-beta.15",
"@ohif/ui": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/extension-cornerstone": "3.13.0-beta.78",
"@ohif/extension-measurement-tracking": "3.13.0-beta.78",
"@ohif/ui": "3.13.0-beta.78",
"dcmjs": "0.49.4",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.29",
"@cornerstonejs/core": "4.15.29",
"@cornerstonejs/tools": "4.15.29",
"@cornerstonejs/adapters": "4.22.8",
"@cornerstonejs/core": "4.22.8",
"@cornerstonejs/tools": "4.22.8",
"classnames": "2.5.1"
}
}
@@ -1,14 +1,11 @@
import { metaData } from '@cornerstonejs/core';
import OHIF, { DicomMetadataStore, utils } from '@ohif/core';
import dcmjs from 'dcmjs';
import OHIF from '@ohif/core';
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;
@@ -75,24 +72,8 @@ const commandsModule = (props: withAppTypes) => {
/**
*
* @param measurementData An array of measurements from the measurements service
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
* as opposed to Finding Sites.
* that you wish to serialize.
*/
downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => {
const srDataset = _generateReport(measurementData, additionalFindingTypes, options);
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
//Create a URL for the binary.
downloadBlob(reportBlob, { filename: 'dicom-sr.dcm' });
},
/**
*
* @param measurementData An array of measurements from the measurements service
* that you wish to serialize.
* @param dataSource The dataSource that you wish to use to persist the data.
* @param dataSource The data source name ('download', 'copyToClipboard', or a named data source).
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
* @return The naturalized report
@@ -103,23 +84,30 @@ const commandsModule = (props: withAppTypes) => {
additionalFindingTypes,
options = {},
}) => {
// Use the @cornerstonejs adapter for converting to/from DICOM
// But it is good enough for now whilst we only have cornerstone as a datasource.
log.info('[DICOMSR] storeMeasurements');
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
const storeFn = commandsManager.runCommand('createStoreFunction', {
dataSource,
defaultFileName: 'dicom-sr.dcm',
});
if (!storeFn) {
log.error('[DICOMSR] No valid store for dataSource:', dataSource);
return Promise.reject({});
}
try {
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
const naturalizedReport = _generateReport(
measurementData,
additionalFindingTypes,
options
);
const { StudyInstanceUID, ContentSequence } = naturalizedReport;
const { ContentSequence } = naturalizedReport;
// The content sequence has 5 or more elements, of which
// the `[4]` element contains the annotation data, so this is
// checking that there is some annotation data present.
if (!ContentSequence?.[4].ContentSequence?.length) {
if (!ContentSequence?.[4]?.ContentSequence?.length) {
console.log('naturalizedReport missing imaging content', naturalizedReport);
throw new Error('Invalid report, no content');
}
@@ -128,22 +116,12 @@ const commandsModule = (props: withAppTypes) => {
}
const onBeforeDicomStore = customizationService.getCustomization('onBeforeDicomStore');
let dicomDict;
if (typeof onBeforeDicomStore === 'function') {
dicomDict = onBeforeDicomStore({ dicomDict, measurementData, naturalizedReport });
}
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
if (StudyInstanceUID) {
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
}
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
DicomMetadataStore.addInstances([naturalizedReport], true);
await storeFn(naturalizedReport, { measurementData, dicomDict });
return naturalizedReport;
} catch (error) {
@@ -166,7 +144,6 @@ const commandsModule = (props: withAppTypes) => {
};
const definitions = {
downloadReport: actions.downloadReport,
storeMeasurements: actions.storeMeasurements,
hydrateStructuredReport: actions.hydrateStructuredReport,
};
@@ -200,8 +200,13 @@ async function _load(
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
srDisplaySet.isLoaded = true;
/** Check currently added displaySets and add measurements if the sources exist */
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
/** Check currently added displaySets and add measurements if the sources exist.
* Walk the SR's study first in default series order (not load order) so SCOORD3D
* FrameOfReference matching picks a stable series when several share FOR. */
const displaySetsForSRPass = utils.sortDisplaySetsCopy(displaySetService.activeDisplaySets, {
studyInstanceUIDFirst: srDisplaySet.StudyInstanceUID,
});
displaySetsForSRPass.forEach(activeDisplaySet => {
_checkIfCanAddMeasurementsToDisplaySet(
srDisplaySet,
activeDisplaySet,
@@ -637,16 +642,35 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
NUMContentItems.forEach(item => {
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
// Handle spatial reference ONLY if ContentSequence exists
// Handle spatial reference ONLY if ContentSequence exists.
// ContentSequence may be a scalar SCOORD or an array when additional named
// SCOORDs (e.g. control points) are nested alongside the primary geometry.
// Pick the primary geometry entry: prefer the SCOORD without a
// ConceptNameCodeSequence (plain polyline), falling back to the first SCOORD.
if (ContentSequence) {
const { ValueType } = ContentSequence;
const scoordItem = Array.isArray(ContentSequence)
? (ContentSequence.find(
cs =>
(cs.ValueType === 'SCOORD' || cs.ValueType === 'SCOORD3D') &&
!cs.ConceptNameCodeSequence
) ?? ContentSequence.find(cs => cs.ValueType === 'SCOORD' || cs.ValueType === 'SCOORD3D'))
: ContentSequence;
if (!scoordItem) {
console.warn(
'ContentSequence array contains no SCOORD or SCOORD3D entry, skipping annotation.'
);
return;
}
const { ValueType } = scoordItem;
if (ValueType !== 'SCOORD' && ValueType !== 'SCOORD3D') {
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
return;
}
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
const coords = _getCoordsFromSCOORDOrSCOORD3D(scoordItem);
if (coords) {
measurement.coords.push(coords);
@@ -80,7 +80,7 @@ export default function hydrateStructuredReport(
const { ReferencedSOPInstanceUID, imageId, frameNumber = 1 } = measurement;
const key = `${ReferencedSOPInstanceUID}:${frameNumber}`;
if (!sopInstanceUIDToImageId[key]) {
if (imageId && !sopInstanceUIDToImageId[key]) {
sopInstanceUIDToImageId[key] = imageId;
}
});
@@ -118,41 +118,17 @@ export default function hydrateStructuredReport(
}
});
// Set the series touched as tracked.
const imageIds = [];
// TODO: notification if no hydratable?
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
toolDataForAnnotationType.forEach(toolData => {
// Add the measurement to toolState
// 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?.frameNumber || 1;
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
if (!imageIds.includes(imageId)) {
imageIds.push(imageId);
}
});
});
let targetStudyInstanceUID;
const SeriesInstanceUIDs = [];
for (let i = 0; i < imageIds.length; i++) {
const imageId = imageIds[i];
if (!imageId) {
continue;
}
const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId);
// Set the series touched as tracked.
const imageIds = getImageIds(hydratableMeasurementsInSR, sopInstanceUIDToImageId);
for (const imageId of imageIds) {
const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId);
if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) {
SeriesInstanceUIDs.push(SeriesInstanceUID);
}
if (!targetStudyInstanceUID) {
targetStudyInstanceUID = StudyInstanceUID;
} else if (targetStudyInstanceUID !== StudyInstanceUID) {
@@ -160,6 +136,31 @@ export default function hydrateStructuredReport(
}
}
// For 3d annotations there are no image IDs,
// so we need to find the display sets by frame of reference to get the SeriesInstanceUIDs
const frameOfReferenceUIDs = getFrameOfReferenceUIDs(
hydratableMeasurementsInSR,
sopInstanceUIDToImageId
);
for (const FrameOfReferenceUID of frameOfReferenceUIDs) {
const displaySetsFOR = displaySetService.getDisplaySetsBy(
ds => ds.FrameOfReferenceUID === FrameOfReferenceUID && !ds.isDerivedDisplaySet
);
const ds = chooseDisplaySet(displaySetsFOR, FrameOfReferenceUID);
if (!ds) {
continue;
}
if (!SeriesInstanceUIDs.includes(ds.SeriesInstanceUID)) {
SeriesInstanceUIDs.push(ds.SeriesInstanceUID);
}
if (!targetStudyInstanceUID) {
targetStudyInstanceUID = ds.StudyInstanceUID;
} else if (targetStudyInstanceUID !== ds.StudyInstanceUID) {
console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.');
}
}
/**
* Gets reference data for what frame of reference and the referenced
* image id, or for 3d measurements, the volumeId to apply this annotation to.
@@ -196,7 +197,7 @@ export default function hydrateStructuredReport(
toolDataForAnnotationType.forEach(toolData => {
toolData.uid = guid();
const referenceData = getReferenceData(toolData);
const { imageId } = referenceData;
const { referencedImageId } = referenceData;
const annotation = {
annotationUID: toolData.annotation.annotationUID,
@@ -241,8 +242,8 @@ export default function hydrateStructuredReport(
locking.setAnnotationLocked(newAnnotationUID, true);
}
if (imageId && !imageIds.includes(imageId)) {
imageIds.push(imageId);
if (referencedImageId && !imageIds.includes(referencedImageId)) {
imageIds.push(referencedImageId);
}
});
});
@@ -255,15 +256,64 @@ export default function hydrateStructuredReport(
};
}
/**
* Gets the unique imageIds from hydratable measurements that have an imageId reference
* (i.e., 2D/SCOORD annotations).
*/
function getImageIds(hydratableMeasurementsInSR, sopInstanceUIDToImageId): string[] {
const imageIds: string[] = [];
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
toolDataForAnnotationType.forEach(toolData => {
// Add the measurement to toolState
// 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?.frameNumber || 1;
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
if (imageId && !imageIds.includes(imageId)) {
imageIds.push(imageId);
}
});
});
return imageIds;
}
/**
* Gets the unique FrameOfReferenceUIDs from hydratable measurements that have no imageId reference
* (i.e., 3D/SCOORD3D annotations). This excludes annotations handled by the getImageIds function.
*/
function getFrameOfReferenceUIDs(hydratableMeasurementsInSR, sopInstanceUIDToImageId): string[] {
const frameOfReferenceUIDs: string[] = [];
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
toolDataForAnnotationType.forEach(toolData => {
const frameNumber = toolData.annotation.data?.frameNumber || 1;
const imageId = sopInstanceUIDToImageId[`${toolData.sopInstanceUid}:${frameNumber}`];
if (!imageId) {
const { FrameOfReferenceUID } = toolData.annotation.metadata;
if (FrameOfReferenceUID && !frameOfReferenceUIDs.includes(FrameOfReferenceUID)) {
frameOfReferenceUIDs.push(FrameOfReferenceUID);
}
}
});
});
return frameOfReferenceUIDs;
}
/**
* For 3d annotations, there are often several display sets which could
* be used to display the annotation. Choose the first annotation with the
* same frame of reference that is reconstructable, or the first display set
* otherwise.
*/
function chooseDisplaySet(displaySets, annotation) {
function chooseDisplaySet(displaySets, reference) {
if (!displaySets?.length) {
console.warn('No display set found for', annotation);
console.warn('No display set found for', reference);
return;
}
if (displaySets.length === 1) {
@@ -3,6 +3,528 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "OHIF extension for 4D volumes data",
"author": "OHIF",
"license": "MIT",
@@ -29,11 +29,11 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/extension-cornerstone": "3.13.0-beta.15",
"@ohif/extension-default": "3.13.0-beta.15",
"@ohif/i18n": "3.13.0-beta.15",
"@ohif/ui": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/extension-cornerstone": "3.13.0-beta.78",
"@ohif/extension-default": "3.13.0-beta.78",
"@ohif/i18n": "3.13.0-beta.78",
"@ohif/ui": "3.13.0-beta.78",
"dcmjs": "0.49.4",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.15.29",
"@cornerstonejs/tools": "4.15.29",
"@cornerstonejs/core": "4.22.8",
"@cornerstonejs/tools": "4.22.8",
"classnames": "2.5.1"
}
}
+573
View File
@@ -3,6 +3,579 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
### Bug Fixes
* **measurements:** read displayName in SplineROI and PlanarFreehandROI reports ([#5908](https://github.com/OHIF/Viewers/issues/5908)) ([27af682](https://github.com/OHIF/Viewers/commit/27af6821b56f2c5be3a5d4ce38d05be6fbce68f4))
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
### Bug Fixes
* **measurement-tracking:** restore tracked state on undo after Delete all ([#5994](https://github.com/OHIF/Viewers/issues/5994)) ([397aa4d](https://github.com/OHIF/Viewers/commit/397aa4d0e361e95dcbd83e0557a9cd84c0b8440a))
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
### Bug Fixes
* **ToolGroupService:** Centralize tool binding persistence and provide API to add and remove persisted bindings. ([#5989](https://github.com/OHIF/Viewers/issues/5989)) ([979d619](https://github.com/OHIF/Viewers/commit/979d619f7ccb426beb75d59721b01c98175d5241))
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
### Features
* **ui-next:** Adds toggle state for ToolButton and Crosshair example ([#5914](https://github.com/OHIF/Viewers/issues/5914)) ([691e267](https://github.com/OHIF/Viewers/commit/691e26731f4f3b3957fe81fc051b09c972696cf6))
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
### Bug Fixes
* **seg:** prevent segmentations from spreading to all viewports before hydration confirmation in 3D four-up ([#5967](https://github.com/OHIF/Viewers/issues/5967)) ([f8ccf9f](https://github.com/OHIF/Viewers/commit/f8ccf9ff2ea9ab7c38bd427514a8ae87902822a3))
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
### Features
* **slice scrollbar:** Integrate ViewportSliceProgressScrollbar with customizations and docs ([#5960](https://github.com/OHIF/Viewers/issues/5960)) ([8fc0dc1](https://github.com/OHIF/Viewers/commit/8fc0dc16e97f3262fdac902ecf2d7c20cb8f78cc))
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
### Bug Fixes
* Use newer ONNX version and load without errors ([#5854](https://github.com/OHIF/Viewers/issues/5854)) ([cc5dc20](https://github.com/OHIF/Viewers/commit/cc5dc201649bcbfb4cfad21141dbd56000d30f53)), closes [#5875](https://github.com/OHIF/Viewers/issues/5875) [#5884](https://github.com/OHIF/Viewers/issues/5884) [#5886](https://github.com/OHIF/Viewers/issues/5886) [#5893](https://github.com/OHIF/Viewers/issues/5893) [#5874](https://github.com/OHIF/Viewers/issues/5874) [#5865](https://github.com/OHIF/Viewers/issues/5865)
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
### Bug Fixes
* **cornerstone:** read FrameOfReferenceUID from display set in viewport service ([#5950](https://github.com/OHIF/Viewers/issues/5950)) ([d219171](https://github.com/OHIF/Viewers/commit/d219171e25caf5a7c575080f63819d6f4fe45016))
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
### Bug Fixes
* **measurement:** Restore viewport interactivity when deleting in-progress Spline or Livewire measurement ([#5905](https://github.com/OHIF/Viewers/issues/5905)) ([7929f08](https://github.com/OHIF/Viewers/commit/7929f0898f1bf882682a912b1c32d93d0944726f))
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
### Bug Fixes
* **SR:** Added support for spline and live wire SR items. ([#5870](https://github.com/OHIF/Viewers/issues/5870)) ([1d4802c](https://github.com/OHIF/Viewers/commit/1d4802c2a38dd75dca7afca87913e100453c9135))
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
### Features
* Add combined build ([#5895](https://github.com/OHIF/Viewers/issues/5895)) ([1df671e](https://github.com/OHIF/Viewers/commit/1df671e9ab67d43f98bf09f6838ddc0a5f220277))
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
### Bug Fixes
* **window level:** The window level value is not displayed by default on all the viewports when selecting common/custom layout and TMTV. ([#5865](https://github.com/OHIF/Viewers/issues/5865)) ([fe1ecfe](https://github.com/OHIF/Viewers/commit/fe1ecfe0cc5d1bcf82686d821f356da8936a8c4b))
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
### Bug Fixes
* **segmentation:** Display "No description S:{series number} {modality}" for segmentations with no label. ([#5874](https://github.com/OHIF/Viewers/issues/5874)) ([7b5d0ce](https://github.com/OHIF/Viewers/commit/7b5d0ce40e5422ccfce6091ac3d7559586cbe57e))
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
### Bug Fixes
* **seg hydration:** auto-hydrate RT struct on second load with disableConfirmationPrompts ([#5875](https://github.com/OHIF/Viewers/issues/5875)) ([6f773f9](https://github.com/OHIF/Viewers/commit/6f773f997214c388409f3af3e9fc0e5b84debc10))
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
### Features
* **ecg:** add DICOM ECG waveform extension ([#5856](https://github.com/OHIF/Viewers/issues/5856)) ([70f76ae](https://github.com/OHIF/Viewers/commit/70f76aeba1b27f8e4de5ec73a7b831428e38e3d2))
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
### Bug Fixes
* **segmentation:** segment bidirectional tool error and show toast notification when no segment is drawn ([#5861](https://github.com/OHIF/Viewers/issues/5861)) ([7e10830](https://github.com/OHIF/Viewers/commit/7e10830d5802e22bda4df6a3f8008e9a414b4cd4))
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
### Bug Fixes
* Update CS3D 4.18.2 ([#5837](https://github.com/OHIF/Viewers/issues/5837)) ([5370b93](https://github.com/OHIF/Viewers/commit/5370b9382e7bfddeadd69bab9e653f5f997a6e19))
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
### Bug Fixes
* **segmentation overlay:** update viewport ds list upon seg delete - OHIF-2425 ([#5729](https://github.com/OHIF/Viewers/issues/5729)) ([b353541](https://github.com/OHIF/Viewers/commit/b35354120a01d89b27844f967c32c052bba8b096))
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
### Bug Fixes
* Add copy to clipboard option for capture ([#5720](https://github.com/OHIF/Viewers/issues/5720)) ([53c67f3](https://github.com/OHIF/Viewers/commit/53c67f361234a0b4160c1b11fafcb143a0723336))
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -40,9 +40,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.15.29",
"@ohif/core": "3.13.0-beta.15",
"@ohif/ui": "3.13.0-beta.15",
"@cornerstonejs/dicom-image-loader": "4.22.8",
"@ohif/core": "3.13.0-beta.78",
"@ohif/ui": "3.13.0-beta.78",
"dcmjs": "0.49.4",
"dicom-parser": "1.8.21",
"hammerjs": "2.0.8",
@@ -53,12 +53,12 @@
},
"dependencies": {
"@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.15.29",
"@cornerstonejs/ai": "4.15.29",
"@cornerstonejs/core": "4.15.29",
"@cornerstonejs/labelmap-interpolation": "4.15.29",
"@cornerstonejs/polymorphic-segmentation": "4.15.29",
"@cornerstonejs/tools": "4.15.29",
"@cornerstonejs/adapters": "4.22.8",
"@cornerstonejs/ai": "4.22.8",
"@cornerstonejs/core": "4.22.8",
"@cornerstonejs/labelmap-interpolation": "4.22.8",
"@cornerstonejs/polymorphic-segmentation": "4.22.8",
"@cornerstonejs/tools": "4.22.8",
"@icr/polyseg-wasm": "0.4.0",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "34.15.1",
@@ -1,13 +1,14 @@
import React, { useEffect, useState } from 'react';
import ViewportImageScrollbar from './ViewportImageScrollbar';
import ViewportSliceProgressScrollbar from './ViewportSliceProgressScrollbar/ViewportSliceProgressScrollbar';
import CustomizableViewportOverlay from './CustomizableViewportOverlay';
import ViewportOrientationMarkers from './ViewportOrientationMarkers';
import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator';
function CornerstoneOverlays(props: withAppTypes) {
const { viewportId, element, scrollbarHeight, servicesManager } = props;
const { cornerstoneViewportService } = servicesManager.services;
const { cornerstoneViewportService, customizationService } = servicesManager.services;
const [imageSliceData, setImageSliceData] = useState({
imageIndex: 0,
numberOfSlices: 0,
@@ -43,17 +44,31 @@ function CornerstoneOverlays(props: withAppTypes) {
}
}
const viewportScrollbarVariant = customizationService.getCustomization('viewportScrollbar.variant');
const useProgressScrollbar = viewportScrollbarVariant !== 'legacy';
return (
<div className="noselect">
<ViewportImageScrollbar
viewportId={viewportId}
viewportData={viewportData}
element={element}
imageSliceData={imageSliceData}
setImageSliceData={setImageSliceData}
scrollbarHeight={scrollbarHeight}
servicesManager={servicesManager}
/>
{useProgressScrollbar ? (
<ViewportSliceProgressScrollbar
viewportId={viewportId}
viewportData={viewportData}
element={element}
imageSliceData={imageSliceData}
setImageSliceData={setImageSliceData}
servicesManager={servicesManager}
/>
) : (
<ViewportImageScrollbar
viewportId={viewportId}
viewportData={viewportData}
element={element}
imageSliceData={imageSliceData}
setImageSliceData={setImageSliceData}
scrollbarHeight={scrollbarHeight}
servicesManager={servicesManager}
/>
)}
<CustomizableViewportOverlay
imageSliceData={imageSliceData}
@@ -67,10 +67,9 @@ function CustomizableViewportOverlay({
}) {
const { cornerstoneViewportService, customizationService, toolGroupService, displaySetService } =
servicesManager.services;
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
const [scale, setScale] = useState(1);
const [annotationState, setAnnotationState] = useState(0);
const { isViewportBackgroundLight: isLight } = useViewportRendering(viewportId);
const { isViewportBackgroundLight: isLight, windowLevel: voi } = useViewportRendering(viewportId);
const { imageIndex } = imageSliceData;
// Historical usage defined the overlays as separate items due to lack of
@@ -110,30 +109,6 @@ function CustomizableViewportOverlay({
};
}, [viewportData, viewportId, instanceNumber, cornerstoneViewportService]);
/**
* Updating the VOI when the viewport changes its voi
*/
useEffect(() => {
const updateVOI = eventDetail => {
const { range } = eventDetail.detail;
if (!range) {
return;
}
const { lower, upper } = range;
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper);
setVOI({ windowCenter, windowWidth });
};
element.addEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
return () => {
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
};
}, [viewportId, viewportData, voi, element]);
const annotationModified = useCallback(evt => {
if (evt.detail.annotation.metadata.toolName === UltrasoundPleuraBLineTool.toolName) {
// Update the annotation state to trigger a re-render
@@ -226,6 +201,7 @@ function CustomizableViewportOverlay({
scale,
instanceNumber,
annotationState,
isLight,
]
);
@@ -391,7 +367,7 @@ function OverlayItem(props) {
title={title}
>
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
<span className="ml-0 mr-2 shrink-0">{value}</span>
<span className="ml-0 shrink-0">{value}</span>
</div>
);
}
@@ -402,6 +378,7 @@ function OverlayItem(props) {
*/
function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
const { windowWidth, windowCenter } = voi;
const { title } = customization;
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
return null;
}
@@ -410,6 +387,7 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
<div
className="overlay-item flex flex-row"
style={{ color: customization?.color }}
title={title}
>
<span className="mr-0.5 shrink-0 opacity-[0.70]">W:</span>
<span className="mr-2.5 shrink-0">{windowWidth.toFixed(0)}</span>
@@ -443,11 +421,13 @@ function InstanceNumberOverlayItem({
customization,
}: OverlayItemProps) {
const { imageIndex, numberOfSlices } = imageSliceData;
const { title } = customization;
return (
<div
className="overlay-item flex flex-row"
style={{ color: (customization && customization.color) || undefined }}
title={title}
>
<span>
{instanceNumber !== undefined && instanceNumber !== null ? (
@@ -0,0 +1,200 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
import {
SmartScrollbar,
SmartScrollbarTrack,
SmartScrollbarFill,
SmartScrollbarIndicator,
SmartScrollbarEndpoints,
} from '@ohif/ui-next';
import { getViewportImageIds } from './helpers';
import {
useLoadedSliceBytes,
useProgressScrollbarMode,
useViewedSliceBytes,
useViewportSliceSync,
} from './hooks';
import { ViewportSliceProgressScrollbarProps } from './types';
function ViewportSliceProgressScrollbar({
viewportData,
viewportId,
element,
imageSliceData,
setImageSliceData,
servicesManager,
}: ViewportSliceProgressScrollbarProps) {
const { cineService, cornerstoneViewportService, customizationService, viewedDataService } =
servicesManager.services;
const showLoadedEndpoints =
customizationService.getCustomization('viewportScrollbar.showLoadedEndpoints') !== false;
const showLoadedFill =
customizationService.getCustomization('viewportScrollbar.showLoadedFill') !== false;
const showViewedFill =
customizationService.getCustomization('viewportScrollbar.showViewedFill') !== false;
const showLoadingPattern =
customizationService.getCustomization('viewportScrollbar.showLoadingPattern') !== false;
const viewedDwellMsRaw = customizationService.getCustomization('viewportScrollbar.viewedDwellMs');
const loadedBatchIntervalMsRaw = customizationService.getCustomization(
'viewportScrollbar.loadedBatchIntervalMs'
);
const viewedDwellMs =
typeof viewedDwellMsRaw === 'number' && viewedDwellMsRaw >= 0 ? viewedDwellMsRaw : 0;
const loadedBatchIntervalMs =
typeof loadedBatchIntervalMsRaw === 'number' && loadedBatchIntervalMsRaw >= 0
? loadedBatchIntervalMsRaw
: 200;
const { numberOfSlices, imageIndex } = imageSliceData;
const imageIds = useMemo(() => getViewportImageIds(viewportData), [viewportData]);
const imageIdToIndex = useMemo(() => {
const map = new Map<string, number>();
for (let i = 0; i < imageIds.length; i++) {
const imageId = imageIds[i];
if (imageId) {
map.set(imageId, i);
}
}
return map;
}, [imageIds]);
const isFullMode = useProgressScrollbarMode({
viewportData,
viewportId,
element,
cornerstoneViewportService,
});
useViewportSliceSync({
viewportData,
viewportId,
element,
cornerstoneViewportService,
setImageSliceData,
});
const {
bytes: loadedBytes,
version: loadedVersion,
isFull: isFullyLoaded,
} = useLoadedSliceBytes({
isFullMode,
numberOfSlices,
viewportData,
imageIds,
imageIdToIndex,
loadedBatchIntervalMs,
});
const { bytes: viewedBytes, version: viewedVersion } = useViewedSliceBytes({
isFullMode,
numberOfSlices,
imageIndex,
imageIds,
imageIdToIndex,
viewedDwellMs,
viewedDataService,
});
const onScrollbarValueChange = targetImageIndex => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport || viewport instanceof VolumeViewport3D) {
return;
}
const { isCineEnabled } = cineService.getState();
if (isCineEnabled) {
cineService.stopClip(element, { viewportId });
cineService.setCine({ id: viewportId, frameRate: undefined, isPlaying: false });
}
csUtils.jumpToSlice(viewport.element, {
imageIndex: targetImageIndex,
debounceLoading: true,
});
};
const isLoading = isFullMode && showLoadingPattern ? !isFullyLoaded : false;
if (!numberOfSlices || numberOfSlices <= 1) {
return null;
}
return (
<div
style={{
position: 'absolute',
right: 0,
top: 0,
height: '100%',
padding: '8px 5px',
zIndex: 10,
}}
>
<div
style={{
position: 'relative',
height: '100%',
width: '11px',
}}
>
<SmartScrollbar
className="absolute inset-0"
value={imageIndex || 0}
total={numberOfSlices}
onValueChange={onScrollbarValueChange}
isLoading={isLoading}
enableKeyboardNavigation={false}
aria-label="Image navigation scrollbar"
indicator={
customizationService.getCustomization('viewportScrollbar.indicator') as
| Record<string, unknown>
| undefined
}
>
<SmartScrollbarTrack>
{isFullMode && showLoadedFill && (
<SmartScrollbarFill
marked={loadedBytes}
version={loadedVersion}
className="bg-neutral/25"
loadingClassName="bg-neutral/50"
/>
)}
{isFullMode && showViewedFill && (
<SmartScrollbarFill
marked={viewedBytes}
version={viewedVersion}
className="bg-primary/35"
loadingClassName="bg-primary/35"
/>
)}
</SmartScrollbarTrack>
<SmartScrollbarIndicator />
{isFullMode && showLoadedEndpoints && (
<SmartScrollbarEndpoints
marked={loadedBytes}
version={loadedVersion}
/>
)}
</SmartScrollbar>
</div>
</div>
);
}
ViewportSliceProgressScrollbar.propTypes = {
viewportData: PropTypes.object,
viewportId: PropTypes.string.isRequired,
element: PropTypes.instanceOf(Element),
imageSliceData: PropTypes.object.isRequired,
setImageSliceData: PropTypes.func.isRequired,
servicesManager: PropTypes.object.isRequired,
};
export default ViewportSliceProgressScrollbar;
@@ -0,0 +1,40 @@
import { Enums, VolumeViewport3D } from '@cornerstonejs/core';
import { ViewportData } from './types';
export function getImageIndexFromEvent(event): number | undefined {
const { imageIndex, newImageIdIndex = imageIndex, imageIdIndex } = event.detail;
return newImageIdIndex ?? imageIdIndex;
}
export function getViewportImageIds(viewportData: ViewportData): string[] {
if (!viewportData?.data?.length) {
return [];
}
const firstData = viewportData.data[0];
const volumeImageIds = (firstData as any).volume?.imageIds as string[] | undefined;
const datumImageIds = (firstData as any).imageIds as string[] | undefined;
return volumeImageIds || datumImageIds || [];
}
export function isProgressFullMode(viewportData: ViewportData, viewport): boolean {
if (!viewportData || !viewport || viewport instanceof VolumeViewport3D) {
return false;
}
if (viewportData.viewportType === Enums.ViewportType.STACK) {
return true;
}
if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
return !!viewport.isInAcquisitionPlane?.();
}
return false;
}
export function getImageIdFromCacheEvent(event): string | undefined {
const detail = event?.detail;
return detail?.imageId || detail?.image?.imageId || detail?.cachedImage?.imageId;
}
@@ -0,0 +1,353 @@
import { useEffect, useRef, useState } from 'react';
import {
cache as cornerstoneCache,
Enums,
eventTarget,
utilities,
VolumeViewport3D,
} from '@cornerstonejs/core';
import { useByteArray } from '@ohif/ui-next';
import { getImageIdFromCacheEvent, getImageIndexFromEvent, isProgressFullMode } from './helpers';
import { ImageSliceData, ViewportData } from './types';
export function useProgressScrollbarMode({
viewportData,
viewportId,
element,
cornerstoneViewportService,
}: {
viewportData: ViewportData;
viewportId: string;
element: HTMLElement;
cornerstoneViewportService: AppTypes.CornerstoneViewportService;
}) {
const [isFullMode, setIsFullMode] = useState(false);
const lastViewPlaneNormalRef = useRef<number[] | null>(null);
/**
* Tracks whether this viewport should render full progress UI (stack or acquisition-plane
* orthographic volume) versus minimal UI. We compute once on setup and recompute on each
* CAMERA_MODIFIED event so stack->MPR transitions and acquisition-plane changes are reflected
* immediately.
*/
useEffect(() => {
if (!viewportData) {
return;
}
const updateMode = () => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const viewportImageData = viewport?.getImageData?.();
const nextViewPlaneNormal = viewport?.getCamera?.()?.viewPlaneNormal as number[] | undefined;
// Do not update the lastViewPlaneNormalRef until we have a valid viewportImageData.
// Without viewportImageData, the viewport is not fully initialized and the isAcquisitionPlane
// check will not be accurate.
if (viewportImageData && nextViewPlaneNormal) {
lastViewPlaneNormalRef.current = [...nextViewPlaneNormal];
}
const nextMode = isProgressFullMode(viewportData, viewport);
setIsFullMode(prevMode => (prevMode === nextMode ? prevMode : nextMode));
};
updateMode();
const onCameraModified = () => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const nextViewPlaneNormal = viewport?.getCamera?.()?.viewPlaneNormal as number[] | undefined;
const previousViewPlaneNormal = lastViewPlaneNormalRef.current;
// Ignore camera updates that keep the same orientation (pan/zoom/scroll).
if (nextViewPlaneNormal && previousViewPlaneNormal) {
if (utilities.isEqual(nextViewPlaneNormal, previousViewPlaneNormal)) {
return;
}
}
updateMode();
};
element.addEventListener(Enums.Events.CAMERA_MODIFIED, onCameraModified);
return () => {
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, onCameraModified);
};
}, [viewportData, viewportId, cornerstoneViewportService, element]);
return isFullMode;
}
export function useViewportSliceSync({
viewportData,
viewportId,
element,
cornerstoneViewportService,
setImageSliceData,
}: {
viewportData: ViewportData;
viewportId: string;
element: HTMLElement;
cornerstoneViewportService: AppTypes.CornerstoneViewportService;
setImageSliceData: (data: ImageSliceData) => void;
}) {
/**
* Keeps shared slice state in sync: first initialize from the live viewport snapshot, then
* subscribe to navigation/render events for incremental updates while users scroll.
*/
useEffect(() => {
if (!viewportData) {
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (viewport && !(viewport instanceof VolumeViewport3D)) {
try {
const currentImageIndex = viewport.getCurrentImageIdIndex();
const currentNumberOfSlices = viewport.getNumberOfSlices();
setImageSliceData({
imageIndex: currentImageIndex,
numberOfSlices: currentNumberOfSlices,
});
} catch (error) {
console.warn(error);
}
}
const { viewportType } = viewportData;
const eventId =
(viewportType === Enums.ViewportType.STACK && Enums.Events.STACK_NEW_IMAGE) ||
(viewportType === Enums.ViewportType.ORTHOGRAPHIC && Enums.Events.VOLUME_NEW_IMAGE) ||
Enums.Events.IMAGE_RENDERED;
const updateIndex = event => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport || viewport instanceof VolumeViewport3D) {
return;
}
const nextImageIndex = getImageIndexFromEvent(event);
if (nextImageIndex == null) {
return;
}
const nextNumberOfSlices = viewport.getNumberOfSlices();
setImageSliceData({
imageIndex: nextImageIndex,
numberOfSlices: nextNumberOfSlices,
});
};
element.addEventListener(eventId, updateIndex);
return () => {
element.removeEventListener(eventId, updateIndex);
};
}, [viewportData, element, viewportId, cornerstoneViewportService, setImageSliceData]);
}
export function useLoadedSliceBytes({
isFullMode,
numberOfSlices,
viewportData,
imageIds,
imageIdToIndex,
loadedBatchIntervalMs,
}: {
isFullMode: boolean;
numberOfSlices: number;
viewportData: ViewportData;
imageIds: string[];
imageIdToIndex: Map<string, number>;
loadedBatchIntervalMs: number;
}) {
const loadedState = useByteArray(numberOfSlices || 0, loadedBatchIntervalMs);
const {
resetWith: resetLoaded,
setByte: setLoadedByte,
clearByte: clearLoadedByte,
} = loadedState;
/**
* Keeps the loaded byte array in sync with Cornerstone cache: seed from cache whenever stack /
* mode / slice count changes, then subscribe so cache add/remove updates stay incremental.
* Seeding runs immediately before registering listeners in the same effect.
*/
useEffect(() => {
if (isFullMode && numberOfSlices) {
resetLoaded(bytes => {
for (let i = 0; i < bytes.length; i++) {
const imageId = imageIds[i];
if (imageId && cornerstoneCache.isLoaded(imageId)) {
bytes[i] = 1;
}
}
});
}
if (!isFullMode || !viewportData) {
return;
}
const markLoaded = event => {
const imageId = getImageIdFromCacheEvent(event);
if (!imageId) {
return;
}
const index = imageIdToIndex.get(imageId);
if (index !== undefined) {
setLoadedByte(index);
}
};
const markRemoved = event => {
const imageId = getImageIdFromCacheEvent(event);
if (!imageId) {
return;
}
const index = imageIdToIndex.get(imageId);
if (index !== undefined) {
clearLoadedByte(index);
}
};
eventTarget.addEventListener(Enums.Events.IMAGE_CACHE_IMAGE_ADDED, markLoaded);
eventTarget.addEventListener(Enums.Events.IMAGE_CACHE_IMAGE_REMOVED, markRemoved);
return () => {
eventTarget.removeEventListener(Enums.Events.IMAGE_CACHE_IMAGE_ADDED, markLoaded);
eventTarget.removeEventListener(Enums.Events.IMAGE_CACHE_IMAGE_REMOVED, markRemoved);
};
}, [
imageIds,
isFullMode,
numberOfSlices,
viewportData,
imageIdToIndex,
resetLoaded,
setLoadedByte,
clearLoadedByte,
]);
return loadedState;
}
export function useViewedSliceBytes({
isFullMode,
numberOfSlices,
imageIndex,
imageIds,
imageIdToIndex,
viewedDwellMs,
viewedDataService,
}: {
isFullMode: boolean;
numberOfSlices: number;
imageIndex: number;
imageIds: string[];
imageIdToIndex: Map<string, number>;
viewedDwellMs: number;
viewedDataService: AppTypes.ViewedDataService;
}) {
const viewedState = useByteArray(numberOfSlices || 0);
const { resetWith: resetViewed, setByte: setViewedByte } = viewedState;
/**
* Keeps the viewed byte array in sync with the global viewed-data store: seed from the store
* whenever stack / mode / slice count changes, then subscribe so `markDataViewed` updates stay
* incremental. Seeding runs immediately before registering the listener in the same effect.
*/
useEffect(() => {
if (isFullMode && numberOfSlices) {
resetViewed(bytes => {
for (let i = 0; i < bytes.length; i++) {
const imageId = imageIds[i];
if (imageId && viewedDataService?.isDataViewed(imageId)) {
bytes[i] = 1;
}
}
});
}
if (!viewedDataService) {
return;
}
const subscription = viewedDataService.subscribeViewedDataChanges(
({
viewedDataId,
viewedDataCleared,
}: {
viewedDataId?: string;
viewedDataCleared?: boolean;
}) => {
if (!isFullMode || !numberOfSlices) {
return;
}
if (viewedDataCleared) {
resetViewed(bytes => {
bytes.fill(0);
});
return;
}
const index = imageIdToIndex.get(viewedDataId);
if (index !== undefined) {
setViewedByte(index);
}
}
);
return () => {
subscription.unsubscribe();
};
}, [
imageIds,
isFullMode,
numberOfSlices,
imageIdToIndex,
resetViewed,
setViewedByte,
viewedDataService,
]);
/**
* Marks slices as viewed in full mode. With `viewedDwellMs === 0`, marking is immediate on
* index change; otherwise a dwell timer is used and cleaned up on subsequent changes/unmount.
*/
useEffect(() => {
if (!isFullMode || !numberOfSlices) {
return;
}
const markViewed = (targetIndex: number) => {
setViewedByte(targetIndex);
const imageId = imageIds[targetIndex];
if (imageId) {
viewedDataService?.markDataViewed(imageId);
}
};
if (viewedDwellMs === 0) {
markViewed(imageIndex || 0);
return;
}
const timerId = window.setTimeout(() => {
markViewed(imageIndex || 0);
}, viewedDwellMs);
return () => {
window.clearTimeout(timerId);
};
}, [
isFullMode,
numberOfSlices,
imageIndex,
imageIds,
setViewedByte,
viewedDwellMs,
viewedDataService,
]);
return viewedState;
}
@@ -0,0 +1 @@
export { default } from './ViewportSliceProgressScrollbar';
@@ -0,0 +1,17 @@
import { StackViewportData, VolumeViewportData } from '../../../types/CornerstoneCacheService';
export type ViewportData = StackViewportData | VolumeViewportData;
export type ImageSliceData = {
imageIndex: number;
numberOfSlices: number;
};
export type ViewportSliceProgressScrollbarProps = {
viewportData: ViewportData | null;
viewportId: string;
element: HTMLElement;
imageSliceData: ImageSliceData;
setImageSliceData: (data: ImageSliceData) => void;
servicesManager: AppTypes.ServicesManager;
};
+94 -31
View File
@@ -145,7 +145,21 @@ function commandsModule({
function _getActiveViewportToolGroupId() {
const viewport = _getActiveViewportEnabledElement();
return toolGroupService.getToolGroupForViewport(viewport.id);
const toolGroup = viewport && toolGroupService.getToolGroupForViewport(viewport.id);
return toolGroup?.id;
}
function _usesPrimaryActivation(bindings) {
if (!bindings?.length) {
return true;
}
return bindings.some(
binding =>
binding.mouseButton === Enums.MouseBindings.Primary &&
binding.modifierKey == null &&
binding.numTouchPoints == null
);
}
function _getActiveSegmentationInfo() {
@@ -398,12 +412,31 @@ function commandsModule({
const { segmentationId: targetId, segmentIndex: targetIndex } = targetSegmentation;
// Check if the segment has voxels before computing bidirectional measurement
const uniqueSegmentIndices = cstUtils.segmentation.getUniqueSegmentIndices(targetId);
const hasVoxels = uniqueSegmentIndices.includes(targetIndex);
if (!hasVoxels) {
uiNotificationService.show({
title: i18n.t('SegmentationPanel:Segment Bidirectional'),
message: i18n.t(
'SegmentationPanel:Draw a segment before using bidirectional measurement'
),
type: 'warning',
});
return;
}
// Get bidirectional measurement data
const bidirectionalData = await cstUtils.segmentation.getSegmentLargestBidirectional({
segmentationId: targetId,
segmentIndices: [targetIndex],
});
if (!bidirectionalData.length) {
return;
}
const activeViewportId = viewportGridService.getActiveViewportId();
// Process each bidirectional measurement
@@ -445,7 +478,7 @@ function commandsModule({
measurement => measurement.segmentIndex === targetIndex
);
commandsManager.run('jumpToMeasurement', {
uid: activeBidirectional.annotationUID,
uid: activeBidirectional?.annotationUID,
});
},
interpolateLabelmap: () => {
@@ -737,6 +770,9 @@ function commandsModule({
* Also marks any provided display measurements isActive value
*/
jumpToMeasurement: ({ uid, displayMeasurements = [] }) => {
if (!uid) {
return;
}
measurementService.jumpToMeasurement(viewportGridService.getActiveViewportId(), uid);
for (const measurement of displayMeasurements) {
measurement.isActive = measurement.uid === uid;
@@ -995,32 +1031,48 @@ function commandsModule({
toolIsEnabled ? toolGroup.setToolDisabled(toolName) : toolGroup.setToolEnabled(toolName);
},
toggleActiveDisabledToolbar({ value, itemId, toolGroupId }) {
toggleActiveDisabledToolbar({ value, itemId, toolGroupId, toolGroupIds }) {
const toolName = itemId || value;
toolGroupId = toolGroupId ?? _getActiveViewportToolGroupId();
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
const resolvedToolGroupIds = toolGroupIds?.length
? toolGroupIds
: [toolGroupId ?? _getActiveViewportToolGroupId()];
const toolIsActive = [
Enums.ToolModes.Active,
Enums.ToolModes.Enabled,
Enums.ToolModes.Passive,
].includes(toolGroup.getToolOptions(toolName).mode);
toolIsActive
? toolGroup.setToolDisabled(toolName)
: actions.setToolActive({ toolName, toolGroupId });
// we should set the previously active tool to active after we set the
// current tool disabled
if (toolIsActive) {
const prevToolName = toolGroup.getPrevActivePrimaryToolName();
if (prevToolName !== toolName) {
actions.setToolActive({ toolName: prevToolName, toolGroupId });
resolvedToolGroupIds.forEach(toolGroupId => {
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
}
const toolIsActive = [
Enums.ToolModes.Active,
Enums.ToolModes.Enabled,
Enums.ToolModes.Passive,
].includes(toolGroup.getToolOptions(toolName).mode);
if (toolIsActive) {
toolGroup.setToolDisabled(toolName);
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
if (_usesPrimaryActivation(bindings)) {
// we should set the previously active tool to active after we set the
// current tool disabled
const prevToolName = toolGroup.getPrevActivePrimaryToolName();
if (prevToolName !== toolName) {
actions.setToolActive({ toolName: prevToolName, toolGroupId });
}
}
return;
}
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
if (_usesPrimaryActivation(bindings)) {
actions.setToolActive({ toolName, toolGroupId, bindings });
} else {
toolGroup.setToolActive(toolName, { bindings });
}
});
},
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [], bindings }) => {
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
@@ -1735,12 +1787,14 @@ function commandsModule({
* Removes a segmentation from the viewport
* @param props.segmentationId - The ID of the segmentation to remove
*/
removeSegmentationFromViewportCommand: ({ segmentationId }) => {
const { segmentationService, viewportGridService } = servicesManager.services;
segmentationService.removeSegmentationRepresentations(
viewportGridService.getActiveViewportId(),
{ segmentationId }
);
removeSegmentationFromViewportCommand: ({ segmentationId: displaySetInstanceUID }) => {
const { viewportGridService } = servicesManager.services;
const viewportId = viewportGridService.getActiveViewportId();
commandsManager.runCommand('removeDisplaySetLayer', {
viewportId,
displaySetInstanceUID,
});
},
/**
@@ -2072,6 +2126,15 @@ function commandsModule({
displaySetInstanceUIDs,
});
if (!updatedViewports?.length) {
return;
}
updatedViewports.forEach(({ viewportId: csViewportId }) => {
const csViewport = cornerstoneViewportService.getCornerstoneViewport(csViewportId);
csViewport?.setNeedsRender?.();
});
actions.setDisplaySetsForViewports({
viewportsToUpdate: updatedViewports.map(viewport => ({
viewportId: viewport.viewportId,
@@ -5,9 +5,7 @@ import {
DropdownMenuSubTrigger,
DropdownMenuPortal,
DropdownMenuSubContent,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuSeparator,
Icons,
} from '@ohif/ui-next';
@@ -19,8 +17,6 @@ interface ExportSegmentationSubMenuItemProps {
allowExport: boolean;
actions: {
storeSegmentation: (segmentationId: string, modality?: string) => Promise<unknown>;
onSegmentationDownloadRTSS: (segmentationId: string) => void;
onSegmentationDownload: (segmentationId: string) => void;
downloadCSVSegmentationReport: (segmentationId: string) => void;
};
}
@@ -37,14 +33,10 @@ export const ExportSegmentationSubMenuItem: React.FC<ExportSegmentationSubMenuIt
<DropdownMenuSub>
<DropdownMenuSubTrigger className="pl-1">
<Icons.Export className="text-foreground" />
<span className="pl-2">{t('Download & Export')}</span>
<span className="pl-2">{t('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 => {
@@ -56,31 +48,6 @@ export const ExportSegmentationSubMenuItem: React.FC<ExportSegmentationSubMenuIt
{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 => {
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Button,
Icons,
@@ -43,6 +43,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
const [thresholdOpacityEnabled, setThresholdOpacityEnabled] = useState(false);
useEffect(() => {
setOptimisticOverlayDisplaySets(overlayDisplaySets);
}, [backgroundDisplaySet?.displaySetInstanceUID, overlayDisplaySets]);
/**
* Change the background display set
*/
@@ -482,7 +486,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri
}
}}
>
<SelectTrigger className="flex-1">
<SelectTrigger
className="flex-1"
data-cy={`overlay-background-ds-select-${viewportId}`}
>
<SelectValue>
{(
backgroundDisplaySet?.SeriesDescription ||
@@ -64,12 +64,6 @@ export const CustomDropdownMenuContent = () => {
context: 'CORNERSTONE',
});
},
onSegmentationDownloadRTSS: segmentationId => {
commandsManager.run('downloadRTSS', { segmentationId });
},
onSegmentationDownload: segmentationId => {
commandsManager.run('downloadSegmentation', { segmentationId });
},
downloadCSVSegmentationReport: segmentationId => {
commandsManager.run('downloadCSVSegmentationReport', { segmentationId });
},
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { ImageModal, FooterAction } from '@ohif/ui-next';
import { ImageModal, FooterAction, toast } from '@ohif/ui-next';
import { useTranslation } from 'react-i18next';
const MAX_TEXTURE_SIZE = 10000;
const DEFAULT_FILENAME = 'image';
@@ -16,6 +16,7 @@ interface ViewportDownloadFormNewProps {
onEnableViewport: (element: HTMLElement) => void;
onDisableViewport: () => void;
onDownload: (filename: string, fileType: string) => void;
onCopyToClipboard: () => void;
warningState: { enabled: boolean; value: string };
}
@@ -32,6 +33,7 @@ function ViewportDownloadFormNew({
onEnableViewport,
onDisableViewport,
onDownload,
onCopyToClipboard,
}: ViewportDownloadFormNewProps) {
const [viewportElement, setViewportElement] = useState<HTMLElement | null>(null);
const [showWarningMessage, setShowWarningMessage] = useState(true);
@@ -138,13 +140,27 @@ function ViewportDownloadFormNew({
<FooterAction.Secondary onClick={onClose}>
{t('Common:Cancel')}
</FooterAction.Secondary>
<FooterAction.Secondary
onClick={async () => {
try {
await onCopyToClipboard();
toast.success(t('Image copied to clipboard'));
onClose();
} catch (error) {
toast.error(t('Failed to copy image to clipboard'));
console.error('Failed to copy to clipboard:', error);
}
}}
>
{t('Copy to Clipboard')}
</FooterAction.Secondary>
<FooterAction.Primary
onClick={() => {
onDownload(filename || DEFAULT_FILENAME, fileType);
onClose();
}}
>
{t('Common:Save')}
{t('Save Image')}
</FooterAction.Primary>
</FooterAction.Right>
</FooterAction>
@@ -25,6 +25,7 @@ export default {
{
id: 'WindowLevel',
inheritsFrom: 'ohif.overlayItem.windowLevel',
title: 'Window Level',
},
{
id: 'ZoomLevel',
@@ -39,6 +40,7 @@ export default {
{
id: 'InstanceNumber',
inheritsFrom: 'ohif.overlayItem.instanceNumber',
title: 'Instance Number',
},
],
};
@@ -0,0 +1,27 @@
/**
* Default customization values for viewport scrollbar behavior.
* The `progress` scrollbar variant is in full mode for stack viewports and
* acquisition-plane orthographic viewports.
* Otherwise, viewports using the `progress` scrollbar show only the indicator.
*
* - `viewportScrollbar.variant`: `'progress' | 'legacy'` (default: `'progress'`)
* - `viewportScrollbar.showLoadedEndpoints`: show loaded-range endpoint caps in full mode
* - `viewportScrollbar.showLoadedFill`: show loaded/cached fill in full mode
* - `viewportScrollbar.showViewedFill`: show viewed fill in full mode
* - `viewportScrollbar.showLoadingPattern`: show loading pattern in full mode while not fully loaded
* - `viewportScrollbar.viewedDwellMs`: delay before marking current image viewed in full mode (ms)
* - `viewportScrollbar.loadedBatchIntervalMs`: loaded-state version batch interval in full mode (ms)
* - `viewportScrollbar.indicator`: optional custom indicator config
*/
export default function getViewportScrollbarCustomization() {
return {
'viewportScrollbar.variant': 'progress',
'viewportScrollbar.showLoadedEndpoints': true,
'viewportScrollbar.showLoadedFill': true,
'viewportScrollbar.showViewedFill': true,
'viewportScrollbar.showLoadingPattern': true,
'viewportScrollbar.viewedDwellMs': 0,
'viewportScrollbar.loadedBatchIntervalMs': 200,
'viewportScrollbar.indicator': {},
};
}
@@ -11,6 +11,7 @@ import windowLevelPresetsCustomization from './customizations/windowLevelPresets
import miscCustomization from './customizations/miscCustomization';
import captureViewportModalCustomization from './customizations/captureViewportModalCustomization';
import viewportDownloadWarningCustomization from './customizations/viewportDownloadWarningCustomization';
import getViewportScrollbarCustomization from './customizations/viewportScrollbarCustomization';
function getCustomizationModule({ commandsManager, servicesManager, extensionManager }) {
return [
@@ -34,6 +35,7 @@ function getCustomizationModule({ commandsManager, servicesManager, extensionMan
...miscCustomization,
...captureViewportModalCustomization,
...viewportDownloadWarningCustomization,
...getViewportScrollbarCustomization(),
},
},
];
@@ -3,6 +3,7 @@ import i18n from '@ohif/i18n';
import { utilities as csUtils, Enums as csEnums } from '@cornerstonejs/core';
import dcmjs from 'dcmjs';
import { dicomWebUtils } from '@ohif/extension-default';
import { buildEcgModule } from './utils/ecgMetadata';
const { MetadataModules } = csEnums;
const { utils } = OHIF;
@@ -155,6 +156,84 @@ export function getDicomMicroscopySopClassHandler({ servicesManager, extensionMa
};
}
export function getSopClassHandlerModule(params) {
return [getDicomMicroscopySopClassHandler(params)];
/**
* DICOM Waveform SOP Class UIDs for ECG / cardiac electrophysiology.
* Reference: https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.5.html
*/
const ECG_SOP_CLASS_UIDS = {
TWELVE_LEAD_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.1',
GENERAL_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.2',
AMBULATORY_ECG_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.1.3',
HEMODYNAMIC_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.2.1',
CARDIAC_ELECTROPHYSIOLOGY_WAVEFORM_STORAGE: '1.2.840.10008.5.1.4.1.1.9.3.1',
};
const ecgSopClassUids = Object.values(ECG_SOP_CLASS_UIDS);
const DicomEcgSOPClassHandlerId =
'@ohif/extension-cornerstone.sopClassHandlerModule.DicomEcgSopClassHandler';
function _getEcgDisplaySetsFromSeries(instances, servicesManager) {
const { userAuthenticationService } = servicesManager.services;
return instances.map(instance => {
const { Modality, SOPInstanceUID } = instance;
const { SeriesDescription, SeriesNumber, SeriesDate } = instance;
const { SeriesInstanceUID, StudyInstanceUID, SOPClassUID } = instance;
const imageId = instance.imageId;
// Register ECG metadata in the OHIF metadata provider so that
// Cornerstone's ECGViewport can retrieve it via metaData.get('ecgModule', imageId).
if (imageId) {
const ecgModule = buildEcgModule(instance, userAuthenticationService);
if (ecgModule) {
csUtils.genericMetadataProvider.addRaw(imageId, {
type: MetadataModules.ECG,
metadata: ecgModule,
});
}
}
return {
Modality,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId: DicomEcgSOPClassHandlerId,
SOPClassUID,
referencedImages: null,
measurements: null,
viewportType: csEnums.ViewportType.ECG,
instances: [instance],
instance,
thumbnailSrc: null,
isDerivedDisplaySet: false,
isLoaded: false,
sopClassUids: ecgSopClassUids,
numImageFrames: 0,
numInstances: 1,
imageIds: imageId ? [imageId] : [],
supportsWindowLevel: false,
label: SeriesDescription || 'ECG',
};
});
}
export function getDicomEcgSopClassHandler({ servicesManager }) {
const getDisplaySetsFromSeries = instances =>
_getEcgDisplaySetsFromSeries(instances, servicesManager);
return {
name: 'DicomEcgSopClassHandler',
sopClassUids: ecgSopClassUids,
getDisplaySetsFromSeries,
};
}
export function getSopClassHandlerModule(params) {
return [getDicomMicroscopySopClassHandler(params), getDicomEcgSopClassHandler(params)];
}
@@ -429,6 +429,40 @@ export default function getToolbarModule({ servicesManager, extensionManager }:
};
},
},
{
name: 'evaluate.cornerstoneTool.toggleWithModifier',
evaluate: ({ viewportId, button, disabledText, toggledOnIcon, defaultIcon }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) {
return;
}
const toolName = toolbarService.getToolNameForButton(button);
if (!toolGroup.hasTool(toolName)) {
return getDisabledState(disabledText);
}
const { mode } = toolGroup.getToolOptions(toolName) ?? {};
const isToggled =
mode === Enums.ToolModes.Passive ||
mode === Enums.ToolModes.Active ||
mode === Enums.ToolModes.Enabled;
const toolBindings = toolGroupService.getToolBindings(toolGroup.id, toolName);
const hasModifierKey =
toolBindings?.some(binding => binding.modifierKey != null) ?? false;
return {
disabled: false,
isActive: false,
isToggled,
icon:
isToggled && hasModifierKey && toggledOnIcon
? toggledOnIcon
: defaultIcon ?? button.props.icon,
};
},
},
{
name: 'evaluate.action',
evaluate: () => {
@@ -10,6 +10,7 @@ import {
BaseVolumeViewport,
cache,
} from '@cornerstonejs/core';
import { getDataIdForViewport } from '../utils/getDataIdForViewport';
import { WindowLevelPreset } from '../types/WindowLevel';
import { ColorbarPositionType, ColorbarOptions, ColorbarProperties } from '../types/Colorbar';
import { VolumeRenderingConfig } from '../types/VolumeRenderingConfig';
@@ -104,11 +105,6 @@ const opacityToLinear = (opacityValue: number): number => {
return Math.pow(opacityValue, 1.0 / GAMMA);
};
const is3DViewport = ({ viewportId, cornerstoneViewportService }) => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
return viewport instanceof VolumeViewport3D;
};
/**
* Hook to access window level functionality for a specific viewport
*
@@ -124,15 +120,17 @@ export function useViewportRendering(
const { cornerstoneViewportService, colorbarService, customizationService } =
servicesManager.services;
const [is3DVolume, setIs3DVolume] = useState(
is3DViewport({ viewportId, cornerstoneViewportService })
);
const [hasColorbar, setHasColorbar] = useState(colorbarService.hasColorbar(viewportId));
const [colorbarPosition, setColorbarPosition] = useState<ColorbarPositionType>(
options?.location ? getPosition(options.location) : 'bottom'
);
const [voiRange, setVoiRange] = useState<{ lower: number; upper: number } | undefined>();
const voiRangeRef = React.useRef<{ lower: number; upper: number } | undefined>();
// Viewport from service; kept in state so we can subscribe to VIEWPORT_DATA_CHANGED when null and re-run effects when it becomes available
const [viewport, setViewport] = useState<Types.IViewport | null>(() =>
viewportId ? (cornerstoneViewportService.getCornerstoneViewport(viewportId) ?? null) : null
);
const [is3DVolume, setIs3DVolume] = useState(viewport instanceof VolumeViewport3D);
const [opacity, setOpacityState] = useState<number | undefined>();
const [opacityLinear, setOpacityLinearState] = useState<number | undefined>();
const [threshold, setThresholdState] = useState<number | undefined>();
@@ -178,6 +176,34 @@ export function useViewportRendering(
);
}, [viewportDisplaySets, presets]);
// Keep viewport in state; when not available, subscribe to VIEWPORT_DATA_CHANGED so we set it when the viewport is ready
useEffect(() => {
if (!viewportId) {
setViewport(null);
return;
}
const vp = cornerstoneViewportService.getCornerstoneViewport(viewportId);
setViewport(vp ?? null);
if (vp) {
return;
}
const { unsubscribe } = cornerstoneViewportService.subscribe(
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
({ viewportId: eventViewportId }) => {
if (eventViewportId !== viewportId) {
return;
}
const next = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (next) {
setViewport(next);
}
}
);
return () => {
unsubscribe();
};
}, [viewportId, cornerstoneViewportService]);
// Calculate pixel value range for the active display set
useEffect(() => {
if (!activeDisplaySetInstanceUID) {
@@ -189,17 +215,15 @@ export function useViewportRendering(
return;
}
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!csViewport) {
if (!viewport) {
return;
}
if (!(csViewport instanceof BaseVolumeViewport)) {
if (!(viewport instanceof BaseVolumeViewport)) {
return;
}
const volumeIds = csViewport.getAllVolumeIds();
const volumeIds = viewport.getAllVolumeIds();
const volumeId = volumeIds.find(id => id.includes(activeDisplaySetInstanceUID));
if (!volumeId) {
@@ -207,7 +231,7 @@ export function useViewportRendering(
}
// only handle volume viewports for now
const imageData = csViewport.getImageData(volumeId);
const imageData = viewport.getImageData(volumeId);
if (!imageData) {
return;
@@ -220,7 +244,7 @@ export function useViewportRendering(
const range = voxelManager.getRange();
setPixelValueRange({ min: range[0], max: range[1] });
}, [activeDisplaySetInstanceUID, displaySetService, cornerstoneViewportService, viewportId]);
}, [activeDisplaySetInstanceUID, displaySetService, viewport]);
// Get the presets specifically for the active display set
const activeDisplaySetPresets = useMemo(() => {
@@ -240,55 +264,47 @@ export function useViewportRendering(
}, [allWindowLevelPresets, activeDisplaySetInstanceUID]);
useEffect(() => {
setIs3DVolume(is3DViewport({ viewportId, cornerstoneViewportService }));
setIs3DVolume(viewport instanceof VolumeViewport3D);
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
// Initialize the VOI range from the viewport
if (viewport && activeDisplaySetInstanceUID) {
try {
let properties;
if (viewport instanceof StackViewport) {
properties = viewport.getProperties();
if (properties.voiRange) {
setVoiRange(properties.voiRange);
voiRangeRef.current = properties.voiRange;
}
} else if (viewport instanceof BaseVolumeViewport) {
// For volume viewports, find the actor for the active display set
const volumeIds = viewport.getAllVolumeIds();
const volumeId = volumeIds.find(id => id.includes(activeDisplaySetInstanceUID));
if (volumeId) {
properties = viewport.getProperties(volumeId);
if (properties?.voiRange) {
setVoiRange(properties.voiRange);
voiRangeRef.current = properties.voiRange;
}
// Get opacity from colormap if available
if (properties?.colormap?.opacity !== undefined) {
const isArray = Array.isArray(properties.colormap.opacity);
const opacity = isArray
? properties.colormap.opacity.reduce((max, current) => Math.max(max, current), 0)
: properties.colormap.opacity;
setOpacityState(opacity);
setOpacityLinearState(opacityToLinear(opacity));
}
// Get threshold from colormap if available
if (properties?.colormap && properties.colormap.threshold !== undefined) {
setThresholdState(properties.colormap.threshold);
}
}
}
} catch (error) {
console.error('Error initializing VOI range:', error);
}
if (!viewport || !activeDisplaySetInstanceUID) {
return;
}
}, [cornerstoneViewportService, viewportId, activeDisplaySetInstanceUID]);
try {
const dataId = getDataIdForViewport(viewport as unknown, activeDisplaySetInstanceUID);
const properties =
dataId != null
? (viewport as Types.IBaseVolumeViewport).getProperties(dataId)
: viewport.getProperties();
if (!properties) {
return;
}
if (properties.voiRange) {
setVoiRange(properties.voiRange);
voiRangeRef.current = properties.voiRange;
}
if (properties.colormap?.opacity !== undefined) {
const opacityVal = properties.colormap.opacity;
const opacity = Array.isArray(opacityVal)
? (opacityVal as unknown as number[]).reduce(
(max, current) => Math.max(max, current),
0
)
: opacityVal;
setOpacityState(opacity);
setOpacityLinearState(opacityToLinear(opacity));
}
if (properties.colormap?.threshold !== undefined) {
setThresholdState(properties.colormap.threshold);
}
} catch (error) {
console.error('Error initializing VOI range:', error);
}
}, [activeDisplaySetInstanceUID, viewport]);
useEffect(() => {
if (!viewportId) {
@@ -313,19 +329,11 @@ export function useViewportRendering(
}, [colorbarService, viewportId]);
useEffect(() => {
if (!viewportId || !activeDisplaySetInstanceUID) {
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
if (!activeDisplaySetInstanceUID || !viewport?.element) {
return;
}
const element = viewport.element;
if (!element) {
return;
}
const updateVOI = eventDetail => {
const { range } = eventDetail.detail;
@@ -369,7 +377,7 @@ export function useViewportRendering(
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
element.removeEventListener(Enums.Events.COLORMAP_MODIFIED, updateColormap);
};
}, [viewportId, activeDisplaySetInstanceUID, cornerstoneViewportService, opacityToLinear]);
}, [activeDisplaySetInstanceUID, viewport, opacityToLinear]);
const validateActiveDisplaySet = useCallback(() => {
if (!activeDisplaySetInstanceUID) {
@@ -560,16 +568,11 @@ export function useViewportRendering(
context: 'CORNERSTONE',
});
},
[commandsManager, viewportId, validateActiveDisplaySet]
[commandsManager, validateActiveDisplaySet, viewport, viewportId]
);
const setOpacity = useCallback(
(opacityValue: number) => {
if (!viewportId) {
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
return;
}
@@ -607,7 +610,7 @@ export function useViewportRendering(
viewport.render();
},
[cornerstoneViewportService, viewportId, validateActiveDisplaySet, opacityToLinear]
[validateActiveDisplaySet, opacityToLinear, viewport]
);
const setOpacityLinear = useCallback(
@@ -621,11 +624,6 @@ export function useViewportRendering(
const setThreshold = useCallback(
(thresholdValue: number) => {
if (!viewportId) {
return;
}
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
return;
}
@@ -653,18 +651,16 @@ export function useViewportRendering(
viewport.render();
},
[cornerstoneViewportService, viewportId, validateActiveDisplaySet]
[validateActiveDisplaySet, viewport]
);
// Get the current colormap for the active display set
const colormap = useMemo(() => {
if (!viewportId || !activeDisplaySetInstanceUID || !viewportDisplaySets?.length) {
if (!activeDisplaySetInstanceUID || !viewportDisplaySets?.length) {
return null;
}
try {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return null;
}
@@ -711,13 +707,7 @@ export function useViewportRendering(
colorbarProperties?.colormaps?.[0]
);
}
}, [
cornerstoneViewportService,
viewportId,
activeDisplaySetInstanceUID,
viewportDisplaySets,
colorbarProperties?.colormaps,
]);
}, [activeDisplaySetInstanceUID, viewportDisplaySets, colorbarProperties?.colormaps, viewport]);
// 3D volume rendering functions
const setVolumeRenderingPreset = useCallback(
@@ -801,7 +791,9 @@ export function useViewportRendering(
setWindowLevel,
setVOIRange,
voiRange,
windowLevel: utilities.windowLevel.toWindowLevel(voiRange?.lower, voiRange?.upper),
windowLevel: voiRange
? utilities.windowLevel.toWindowLevel(voiRange?.lower, voiRange?.upper)
: { windowCenter: null, windowWidth: null },
// Colorbar functions
hasColorbar,
@@ -9,7 +9,7 @@ import { useSystem } from '@ohif/core';
const excludedModalities = ['SM', 'OT', 'DOC', 'ECG'];
function mapSegmentationToDisplay(segmentation, customizationService) {
const { label, segments } = segmentation;
const { label, segments, fallbackLabel } = segmentation;
// Get the readable text mapping once
const readableTextMap = customizationService.getCustomization('panelSegmentation.readableText');
@@ -76,6 +76,7 @@ function mapSegmentationToDisplay(segmentation, customizationService) {
return {
...segmentation,
label,
fallbackLabel,
segments: updatedSegments,
};
}
+87 -27
View File
@@ -20,6 +20,34 @@ export const HYDRATE_SEG_SYNC_GROUP = {
},
};
const viewportStructure = {
layoutType: 'grid',
properties: {
rows: 1,
columns: 3,
layoutOptions: [
{
x: 0,
y: 0,
width: 1 / 3,
height: 1,
},
{
x: 1 / 3,
y: 0,
width: 1 / 3,
height: 1,
},
{
x: 2 / 3,
y: 0,
width: 1 / 3,
height: 1,
},
],
},
};
export const mpr: Types.HangingProtocol.Protocol = {
id: 'mpr',
name: i18n.t('Hps:MPR'),
@@ -53,33 +81,7 @@ export const mpr: Types.HangingProtocol.Protocol = {
stages: [
{
name: 'MPR 1x3',
viewportStructure: {
layoutType: 'grid',
properties: {
rows: 1,
columns: 3,
layoutOptions: [
{
x: 0,
y: 0,
width: 1 / 3,
height: 1,
},
{
x: 1 / 3,
y: 0,
width: 1 / 3,
height: 1,
},
{
x: 2 / 3,
y: 0,
width: 1 / 3,
height: 1,
},
],
},
},
viewportStructure,
viewports: [
{
viewportOptions: {
@@ -134,5 +136,63 @@ export const mpr: Types.HangingProtocol.Protocol = {
},
],
},
{
name: 'MPR Reformat 1x3',
viewportStructure,
viewports: [
{
viewportOptions: {
viewportId: 'mpr-axial',
toolGroupId: 'mpr',
viewportType: 'volume',
orientation: 'axial_reformat',
initialImageOptions: {
preset: 'middle',
},
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
},
displaySets: [
{
id: 'activeDisplaySet',
},
],
},
{
viewportOptions: {
viewportId: 'mpr-sagittal',
toolGroupId: 'mpr',
viewportType: 'volume',
orientation: 'sagittal_reformat',
initialImageOptions: {
preset: 'middle',
},
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
},
displaySets: [
{
id: 'activeDisplaySet',
},
],
},
{
viewportOptions: {
viewportId: 'mpr-coronal',
toolGroupId: 'mpr',
viewportType: 'volume',
orientation: 'coronal_reformat',
initialImageOptions: {
preset: 'middle',
},
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
},
displaySets: [
{
id: 'activeDisplaySet',
},
],
},
],
},
],
};
+3
View File
@@ -21,6 +21,7 @@ import SegmentationService from './services/SegmentationService';
import CornerstoneCacheService from './services/CornerstoneCacheService';
import CornerstoneViewportService from './services/ViewportService/CornerstoneViewportService';
import ColorbarService from './services/ColorbarService';
import ViewedDataService from './services/ViewedDataService';
import * as CornerstoneExtensionTypes from './types';
import { toolNames } from './initCornerstoneTools';
@@ -180,6 +181,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
useSelectedSegmentationsForViewportStore
.getState()
.clearSelectedSegmentationsForViewportStore();
servicesManager.services.viewedDataService?.clearViewedData();
segmentationService.removeAllSegmentations();
},
@@ -196,6 +198,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
servicesManager.registerService(SegmentationService.REGISTRATION);
servicesManager.registerService(CornerstoneCacheService.REGISTRATION);
servicesManager.registerService(ColorbarService.REGISTRATION);
servicesManager.registerService(ViewedDataService.REGISTRATION);
const { syncGroupService } = servicesManager.services;
syncGroupService.registerCustomSynchronizer('frameview', createFrameViewSynchronizer);
@@ -1,5 +1,5 @@
import { eventTarget, Types } from '@cornerstonejs/core';
import { Enums, annotation } from '@cornerstonejs/tools';
import { eventTarget, Types, utilities as csUtils } from '@cornerstonejs/core';
import { Enums, annotation, cancelActiveManipulations } from '@cornerstonejs/tools';
import { DicomMetadataStore } from '@ohif/core';
import * as CSExtensionEnums from './enums';
@@ -17,6 +17,8 @@ const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION }
const { removeAnnotation } = annotation.state;
const csToolsEvents = Enums.Events;
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const initMeasurementService = (
measurementService,
displaySetService,
@@ -354,7 +356,7 @@ const connectMeasurementServiceToTools = ({
const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } =
measurementService.EVENTS;
measurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements }) => {
measurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements, trackingContext }) => {
if (!Object.keys(measurements).length) {
return;
}
@@ -373,6 +375,29 @@ const connectMeasurementServiceToTools = ({
options: { deleting: true },
});
}
// If tracking context was provided, push a memo that keeps XState in sync with
// Cornerstone's annotation history across unlimited undo/redo cycles:
// undo → re-populate trackedStudy/trackedSeries so the panel reflects the
// restored annotations.
// redo → wipe trackedStudy/trackedSeries because the annotations have been
// re-deleted by their own Cornerstone memos; no measurements are
// deleted here (they are already gone), so CLEAR_TRACKING_CONTEXT is
// used instead of UNTRACK_ALL to avoid a double-delete.
if (trackingContext) {
DefaultHistoryMemo.push({
id: csUtils.uuidv4(),
operationType: 'trackingState',
restoreMemo(undo?: boolean) {
if (undo === true) {
commandsManager.run('restoreTrackedSeries', trackingContext);
} else if (undo === false) {
commandsManager.run('clearTrackedSeries');
}
},
});
}
commandsManager.run('endRecordingForAnnotationGroup');
// trigger a render
@@ -504,6 +529,12 @@ const connectMeasurementServiceToTools = ({
if (source?.name && source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) {
return;
}
// Cancel any active tool manipulation (e.g., Spline/Livewire) to avoid leaving the tool
// in a drawing state after deleting a not completed measurement, which can block viewport interactivity.
const element = getActiveViewportEnabledElement(viewportGridService)?.viewport?.element;
if (element) {
cancelActiveManipulations(element);
}
const removedAnnotation = annotation.state.getAnnotation(removedMeasurementId);
removeAnnotation(removedMeasurementId);
// Ensure `removedAnnotation` is available before triggering the memo,
@@ -1,6 +1,7 @@
import { PubSubService, Types as OhifTypes } from '@ohif/core';
import { RENDERING_ENGINE_ID } from '../ViewportService/constants';
import { getRenderingEngine } from '@cornerstonejs/core';
import { getDataIdForViewport } from '../../utils/getDataIdForViewport';
import { ColorbarOptions, ChangeTypes } from '../../types/Colorbar';
export default class ColorbarService extends PubSubService {
@@ -38,25 +39,6 @@ export default class ColorbarService extends PubSubService {
this.servicesManager = servicesManager;
}
/**
* Gets the appropriate data ID for a viewport and display set
* @param viewport - The viewport instance
* @param displaySetInstanceUID - The display set instance UID to identify data
* @returns The appropriate data ID for the viewport type (volumeId for volume viewports, undefined for stack)
*/
private getDataIdForViewport(viewport, displaySetInstanceUID: string): string | undefined {
// For volume viewports, find the matching volumeId
if (viewport.getAllVolumeIds) {
const volumeIds = viewport.getAllVolumeIds() || [];
return volumeIds.length > 0
? volumeIds.find(id => id.includes(displaySetInstanceUID)) || undefined
: undefined;
}
// For other viewports, no specific dataId is needed for now
return undefined;
}
/**
* Adds a colorbar to a specific viewport identified by `viewportId`, using the provided `displaySetInstanceUIDs` and `options`.
* This method prepares the colorbar state that will be used by the ViewportColorbarsContainer component.
@@ -92,7 +74,7 @@ export default class ColorbarService extends PubSubService {
return;
}
const dataId = this.getDataIdForViewport(viewport, displaySetInstanceUID);
const dataId = getDataIdForViewport(viewport, displaySetInstanceUID);
const properties = dataId ? viewport.getProperties(dataId) : viewport.getProperties();
const colormap = properties?.colormap;
@@ -246,7 +228,7 @@ export default class ColorbarService extends PubSubService {
}
// Get the appropriate dataId for this viewport/displaySet combination
const dataId = this.getDataIdForViewport(viewport, displaySetInstanceUID);
const dataId = getDataIdForViewport(viewport, displaySetInstanceUID);
// Set properties with or without dataId based on what the viewport supports
viewport.setProperties({ colormap }, dataId);
@@ -1120,6 +1120,7 @@ describe('SegmentationService', () => {
isDynamicVolume: false,
SeriesNumber: 1,
SeriesDescription: 'Series Description',
Modality: 'SEG',
} as unknown as AppTypes.DisplaySet;
jest
@@ -1145,6 +1146,7 @@ describe('SegmentationService', () => {
info: 'S1: Series Description',
},
label: 'Segmentation 2',
fallbackLabel: 'S:1 SEG',
segments: {
'1': {
active: true,
@@ -1175,6 +1177,7 @@ describe('SegmentationService', () => {
dynamicVolumeInfo: {
timePoints: ['timePoint1', 'timePoint2', 'timePoint3'],
},
Modality: 'SEG',
} as unknown as AppTypes.DisplaySet;
jest
@@ -1212,6 +1215,7 @@ describe('SegmentationService', () => {
info: 'S1: Series Description',
},
label: 'Segmentation 2',
fallbackLabel: 'S:1 SEG',
segments: {
'1': {
active: true,
@@ -1320,6 +1324,8 @@ describe('SegmentationService', () => {
},
SeriesDate: '2025-01-01',
SeriesDescription: 'Series Description',
Modality: 'SEG',
SeriesNumber: 1,
};
const referencedDisplaySet = {
@@ -1379,6 +1385,7 @@ describe('SegmentationService', () => {
const expectedSegmentation = {
config: {
label: segDisplaySet.SeriesDescription,
fallbackLabel: 'S:1 SEG',
segments: {
'1': {
active: false,
@@ -1534,7 +1541,7 @@ describe('SegmentationService', () => {
it('should create a segmentation for a RTSTRUCT display set', async () => {
const segmentationId = 'segmentationId';
const rtStructDisplaySet = {
modality: 'RTSTRUCT',
Modality: 'RTSTRUCT',
displaySetInstanceUID: 'display-set-uid',
referencedDisplaySetInstanceUID: 'existent-display-set-uid',
SeriesDate: '2025-01-01',
@@ -1544,6 +1551,7 @@ describe('SegmentationService', () => {
ROIContours: [{}, {}, {}],
frameOfReferenceUID: 'frameOfReferenceUID',
},
SeriesNumber: 1,
};
const referencedDisplaySet = {
@@ -1650,6 +1658,7 @@ describe('SegmentationService', () => {
const expectedSegmentation = {
config: {
label: rtStructDisplaySet.SeriesDescription,
fallbackLabel: 'S:1 RTSTRUCT',
segments: {
'1': {
active: false,
@@ -2567,12 +2576,12 @@ describe('SegmentationService', () => {
describe('clearSegmentationRepresentations', () => {
it('should clear the segmentation representations', () => {
const viewportId = 'viewportId';
jest.spyOn(service, 'removeSegmentationRepresentations').mockReturnValue(undefined);
jest.spyOn(service, 'removeRepresentationsFromViewport').mockReturnValue(undefined);
service.clearSegmentationRepresentations(viewportId);
expect(service.removeSegmentationRepresentations).toHaveBeenCalledTimes(1);
expect(service.removeSegmentationRepresentations).toHaveBeenCalledWith(viewportId);
expect(service.removeRepresentationsFromViewport).toHaveBeenCalledTimes(1);
expect(service.removeRepresentationsFromViewport).toHaveBeenCalledWith(viewportId);
});
});
@@ -2600,7 +2609,7 @@ describe('SegmentationService', () => {
});
});
describe('removeSegmentationRepresentations', () => {
describe('removeRepresentationsFromViewport', () => {
it('should remove the segmentation representations', () => {
const viewportId = 'viewportId';
const specifier = {
@@ -2609,7 +2618,7 @@ describe('SegmentationService', () => {
};
jest.spyOn(cstSegmentation, 'removeSegmentationRepresentations').mockReturnValue(undefined);
service.removeSegmentationRepresentations(viewportId, specifier);
service.removeRepresentationsFromViewport(viewportId, specifier);
expect(cstSegmentation.removeSegmentationRepresentations).toHaveBeenCalledTimes(1);
expect(cstSegmentation.removeSegmentationRepresentations).toHaveBeenCalledWith(
@@ -55,6 +55,7 @@ export type SegmentationRepresentation = cstTypes.SegmentationRepresentation & {
viewportId: string;
id: string;
label: string;
fallbackLabel?: string;
styles: cstTypes.RepresentationStyle;
segments: {
[key: number]: SegmentRepresentation;
@@ -247,7 +248,7 @@ class SegmentationService extends PubSubService {
eventTarget.removeEventListener(
csToolsEnums.Events.SEGMENTATION_REMOVED,
this._onSegmentationModifiedFromSource
this._onSegmentationRemovedFromSource
);
eventTarget.removeEventListener(
@@ -267,7 +268,7 @@ class SegmentationService extends PubSubService {
eventTarget.removeEventListener(
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_REMOVED,
this._onSegmentationRepresentationModifiedFromSource
this._onSegmentationRepresentationRemovedFromSource
);
eventTarget.removeEventListener(
@@ -430,6 +431,7 @@ class SegmentationService extends PubSubService {
},
config: {
label,
fallbackLabel: `S:${displaySet.SeriesNumber} ${displaySet.Modality}`,
segments:
options?.segments && Object.keys(options.segments).length > 0
? options.segments
@@ -583,6 +585,7 @@ class SegmentationService extends PubSubService {
},
config: {
label: segDisplaySet.SeriesDescription,
fallbackLabel: `S:${segDisplaySet.SeriesNumber} ${segDisplaySet.Modality}`,
segments,
},
};
@@ -664,6 +667,7 @@ class SegmentationService extends PubSubService {
},
config: {
label: rtDisplaySet.SeriesDescription,
fallbackLabel: `S:${rtDisplaySet.SeriesNumber} ${rtDisplaySet.Modality}`,
},
};
@@ -1284,14 +1288,14 @@ class SegmentationService extends PubSubService {
/**
* Clears segmentation representations from the viewport.
* Unlike removeSegmentationRepresentations, this doesn't update
* Unlike removeRepresentationsFromViewport, this doesn't update
* removed display set and representation maps.
* We track removed segmentations manually to avoid re-adding them
* when the display set is added again.
* @param viewportId - The viewport ID to clear segmentation representations from.
*/
public clearSegmentationRepresentations(viewportId: string): void {
this.removeSegmentationRepresentations(viewportId);
this.removeRepresentationsFromViewport(viewportId);
}
/**
@@ -1307,7 +1311,7 @@ class SegmentationService extends PubSubService {
}
/**
* It removes the segmentation representations from the viewport.
* Removes segmentation representations from the viewport.
* @param viewportId - The viewport id to remove the segmentation representations from.
* @param specifier - The specifier to remove the segmentation representations.
*
@@ -1317,7 +1321,7 @@ class SegmentationService extends PubSubService {
* If a type specifier is provided, only the segmentation representation with the specified type are removed.
* If both a segmentationId and type specifier are provided, only the segmentation representation with the specified segmentationId and type are removed.
*/
public removeSegmentationRepresentations(
public removeRepresentationsFromViewport(
viewportId: string,
specifier: {
segmentationId?: string;
@@ -1842,6 +1846,7 @@ class SegmentationService extends PubSubService {
id: id,
segmentationId,
label: segmentation.label,
fallbackLabel: segmentation.fallbackLabel,
active,
type,
visible,
@@ -1861,7 +1866,7 @@ class SegmentationService extends PubSubService {
eventTarget.addEventListener(
csToolsEnums.Events.SEGMENTATION_REMOVED,
this._onSegmentationModifiedFromSource
this._onSegmentationRemovedFromSource
);
eventTarget.addEventListener(
@@ -1881,7 +1886,7 @@ class SegmentationService extends PubSubService {
eventTarget.addEventListener(
csToolsEnums.Events.SEGMENTATION_REPRESENTATION_REMOVED,
this._onSegmentationRepresentationModifiedFromSource
this._onSegmentationRepresentationRemovedFromSource
);
eventTarget.addEventListener(
@@ -2120,6 +2125,14 @@ class SegmentationService extends PubSubService {
});
};
private _onSegmentationRepresentationRemovedFromSource = evt => {
const { segmentationId, viewportId } = evt.detail;
this._broadcastEvent(this.EVENTS.SEGMENTATION_REPRESENTATION_REMOVED, {
segmentationId,
viewportId,
});
};
private _onSegmentationModifiedFromSource = (
evt: cstTypes.EventTypes.SegmentationModifiedEventType
) => {
@@ -2140,6 +2153,16 @@ class SegmentationService extends PubSubService {
});
};
private _onSegmentationRemovedFromSource = (
evt: cstTypes.EventTypes.SegmentationRemovedEventType
) => {
const { segmentationId } = evt.detail;
this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, {
segmentationId,
});
};
private _onAnnotationCutMergeProcessCompletedFromSource = evt => {
const { segmentationId } = evt.detail;
this._broadcastEvent(this.EVENTS.SEGMENTATION_ANNOTATION_CUT_MERGE_PROCESS_COMPLETED, {
@@ -66,7 +66,15 @@ const segmentationRepresentationModifiedCallback = async (
const sharedDisplaySetExists = isAnyDisplaySetCommon(sourceDisplaySetUIDs, targetDisplaySetUIDs);
if (!sharedDisplaySetExists && !viewport.getFrameOfReferenceUID()) {
const targetFrameOfReferenceUID = viewport.getFrameOfReferenceUID();
const sourceFrameOfReferenceUID =
getEnabledElementByViewportId(sourceViewportId)?.viewport?.getFrameOfReferenceUID();
if (!sharedDisplaySetExists && !targetFrameOfReferenceUID) {
return;
}
if (!sharedDisplaySetExists && targetFrameOfReferenceUID !== sourceFrameOfReferenceUID) {
return;
}
@@ -23,6 +23,12 @@ type Tools = {
disabled?: Tool[];
};
type ToolBindings = Array<Record<string, unknown>>;
type PersistedToolBindings = Record<string, Record<string, ToolBindings>>;
type ApplyToolBindingsOptions = {
replaceExisting?: boolean;
};
export default class ToolGroupService {
public static REGISTRATION = {
name: 'toolGroupService',
@@ -36,7 +42,10 @@ export default class ToolGroupService {
cornerstoneViewportService: any;
viewportGridService: any;
uiNotificationService: any;
customizationService: any;
private toolGroupIds: Set<string> = new Set();
private toolBindingsMap: Map<string, Map<string, Array<Record<string, unknown>>>> = new Map();
private defaultToolBindingsMap: Map<string, Map<string, ToolBindings>> = new Map();
/**
* Service-specific
*/
@@ -44,11 +53,17 @@ export default class ToolGroupService {
EVENTS: { [key: string]: string };
constructor(servicesManager: AppTypes.ServicesManager) {
const { cornerstoneViewportService, viewportGridService, uiNotificationService } =
const {
cornerstoneViewportService,
viewportGridService,
uiNotificationService,
customizationService,
} =
servicesManager.services;
this.cornerstoneViewportService = cornerstoneViewportService;
this.viewportGridService = viewportGridService;
this.uiNotificationService = uiNotificationService;
this.customizationService = customizationService;
this.listeners = {};
this.EVENTS = EVENTS;
Object.assign(this, pubSubServiceInterface);
@@ -122,6 +137,8 @@ export default class ToolGroupService {
public destroy(): void {
ToolGroupManager.destroy();
this.toolGroupIds = new Set();
this.toolBindingsMap.clear();
this.defaultToolBindingsMap.clear();
eventTarget.removeEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated);
}
@@ -198,6 +215,7 @@ export default class ToolGroupService {
// this.changeConfigurationIfNecessary(toolGroup, volumeId);
this._addTools(toolGroup, tools, configs);
this._setToolsMode(toolGroup, tools);
this._loadPersistedBindings(toolGroupId);
}
public createToolGroupAndAddTools(toolGroupId: string, tools: Array<Tool>): Types.IToolGroup {
@@ -242,34 +260,159 @@ export default class ToolGroupService {
return this.getToolGroup(toolGroupId)?.getActivePrimaryMouseButtonTool();
}
public getToolBindings(
toolGroupId: string,
toolName: string
): ToolBindings | undefined {
return this.toolBindingsMap.get(toolGroupId)?.get(toolName);
}
public setToolBindings(toolGroupId: string, toolName: string, bindings: ToolBindings): void {
if (!this.toolBindingsMap.has(toolGroupId)) {
this.toolBindingsMap.set(toolGroupId, new Map());
}
this.toolBindingsMap.get(toolGroupId).set(toolName, this._cloneToolBindings(bindings));
}
public getDefaultToolBindings(toolGroupId: string, toolName: string): ToolBindings | undefined {
const defaultBindings = this.defaultToolBindingsMap.get(toolGroupId)?.get(toolName);
return defaultBindings ? this._cloneToolBindings(defaultBindings) : undefined;
}
public persistToolBindings(toolGroupId: string, toolName: string, bindings: ToolBindings): void {
const persistedBindings = this._readPersistedToolBindings();
if (!persistedBindings[toolGroupId]) {
persistedBindings[toolGroupId] = {};
}
persistedBindings[toolGroupId][toolName] = bindings;
this._writePersistedToolBindings(persistedBindings);
}
public removePersistedToolBindings(toolGroupId: string, toolName?: string): void {
const persistedBindings = this._readPersistedToolBindings();
if (!persistedBindings[toolGroupId]) {
return;
}
if (toolName) {
delete persistedBindings[toolGroupId][toolName];
if (!Object.keys(persistedBindings[toolGroupId]).length) {
delete persistedBindings[toolGroupId];
}
} else {
delete persistedBindings[toolGroupId];
}
this._writePersistedToolBindings(persistedBindings);
}
/**
* Applies the currently tracked bindings to the runtime tool instance.
*
* Note: this method may activate tools that are currently Passive or Enabled.
* Assigning bindings is treated as making the tool interactable.
*/
public applyToolBindings(
toolGroupId: string,
toolName: string,
options: ApplyToolBindingsOptions = {}
): void {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
const bindings = this.getToolBindings(toolGroupId, toolName);
if (!bindings) {
return;
}
const { mode } = toolGroup.getToolOptions(toolName);
if (
mode === Enums.ToolModes.Active ||
mode === Enums.ToolModes.Passive ||
mode === Enums.ToolModes.Enabled
) {
if (options.replaceExisting) {
// Opt-in behavior for callers that need replacement semantics.
toolGroup.setToolDisabled(toolName);
}
toolGroup.setToolActive(toolName, { bindings });
}
}
public getAllToolBindings(): Array<{
toolGroupId: string;
toolName: string;
bindings: Array<Record<string, unknown>>;
}> {
const result = [];
for (const [toolGroupId, toolMap] of this.toolBindingsMap) {
for (const [toolName, bindings] of toolMap) {
result.push({ toolGroupId, toolName, bindings });
}
}
return result;
}
private _setToolsMode(toolGroup, tools) {
const { active, passive, enabled, disabled } = tools;
if (active) {
active.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
}
toolGroup.setToolActive(toolName, { bindings });
});
}
if (passive) {
passive.forEach(({ toolName }) => {
passive.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
}
toolGroup.setToolPassive(toolName);
});
}
if (enabled) {
enabled.forEach(({ toolName }) => {
enabled.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
}
toolGroup.setToolEnabled(toolName);
});
}
if (disabled) {
disabled.forEach(({ toolName }) => {
disabled.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
}
toolGroup.setToolDisabled(toolName);
});
}
}
private _setDefaultToolBindingsIfMissing(
toolGroupId: string,
toolName: string,
bindings: ToolBindings
): void {
if (!this.defaultToolBindingsMap.has(toolGroupId)) {
this.defaultToolBindingsMap.set(toolGroupId, new Map());
}
const toolMap = this.defaultToolBindingsMap.get(toolGroupId);
if (!toolMap.has(toolName)) {
toolMap.set(toolName, this._cloneToolBindings(bindings));
}
}
private _addTools(toolGroup, tools) {
const addTools = tools => {
tools.forEach(({ toolName, parentTool, configuration }) => {
@@ -300,6 +443,71 @@ export default class ToolGroupService {
}
}
private _loadPersistedBindings(toolGroupId: string): void {
const toolGroupBindings = this._readPersistedToolBindings()[toolGroupId];
if (!toolGroupBindings) {
return;
}
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
for (const [toolName, bindings] of Object.entries(toolGroupBindings)) {
this.setToolBindings(toolGroupId, toolName, bindings as ToolBindings);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
continue;
}
const { mode } = toolGroup.getToolOptions(toolName);
if (mode === Enums.ToolModes.Active) {
this.applyToolBindings(toolGroupId, toolName, { replaceExisting: true });
}
}
}
private _readPersistedToolBindings(): PersistedToolBindings {
try {
const stored = localStorage.getItem(this._getToolBindingsStorageKey());
if (!stored) {
return {};
}
const parsed = JSON.parse(stored);
if (!parsed || typeof parsed !== 'object') {
return {};
}
return parsed as PersistedToolBindings;
} catch {
// ignore corrupt localStorage
return {};
}
}
private _writePersistedToolBindings(bindings: PersistedToolBindings): void {
const storageKey = this._getToolBindingsStorageKey();
if (!Object.keys(bindings).length) {
localStorage.removeItem(storageKey);
return;
}
localStorage.setItem(storageKey, JSON.stringify(bindings));
}
private _cloneToolBindings(bindings: ToolBindings): ToolBindings {
return bindings.map(binding => ({ ...binding }));
}
private _getToolBindingsStorageKey(): string {
const customizationValue = this.customizationService?.getCustomization(
'ohif.userPreferences.toolBindingsStorageKey'
);
return typeof customizationValue === 'string' && customizationValue.length > 0
? customizationValue
: 'user-preferred-tool-bindings';
}
private _onToolActivated = (evt: Types.EventTypes.ToolActivatedEventType) => {
const { toolGroupId, toolName, toolBindingsOptions } = evt.detail;
const isPrimaryTool = toolBindingsOptions.bindings?.some(
@@ -0,0 +1,58 @@
import { PubSubService } from '@ohif/core';
export type ViewedDataPayload = {
viewedDataId?: string;
viewedDataCleared?: boolean;
};
class ViewedDataService extends PubSubService {
public static readonly EVENTS = {
VIEWED_DATA_CHANGED: 'event::viewedDataChanged',
};
public static REGISTRATION = {
name: 'viewedDataService',
altName: 'ViewedDataService',
create: () => {
return new ViewedDataService();
},
};
private viewedDataIds = new Set<string>();
constructor() {
super(ViewedDataService.EVENTS);
}
public markDataViewed(dataId: string): void {
if (!dataId || this.viewedDataIds.has(dataId)) {
return;
}
this.viewedDataIds.add(dataId);
this._broadcastEvent(this.EVENTS.VIEWED_DATA_CHANGED, {
viewedDataId: dataId,
});
}
public isDataViewed(dataId: string): boolean {
if (!dataId) {
return false;
}
return this.viewedDataIds.has(dataId);
}
public clearViewedData(): void {
this.viewedDataIds.clear();
this._broadcastEvent(this.EVENTS.VIEWED_DATA_CHANGED, {
viewedDataCleared: true,
});
}
public subscribeViewedDataChanges(listener: (payload: ViewedDataPayload) => void) {
return this.subscribe(this.EVENTS.VIEWED_DATA_CHANGED, listener);
}
}
export default ViewedDataService;
@@ -0,0 +1,3 @@
import ViewedDataService from './ViewedDataService';
export default ViewedDataService;
@@ -9,6 +9,7 @@ import {
utilities as csUtils,
VolumeViewport,
VolumeViewport3D,
ECGViewport,
cache,
Enums as csEnums,
BaseVolumeViewport,
@@ -48,6 +49,47 @@ const EVENTS = {
const MIN_STACK_VIEWPORTS_TO_ENQUEUE_RESIZE = 12;
const MIN_VOLUME_VIEWPORTS_TO_ENQUEUE_RESIZE = 6;
function getVolumeActorReferencedIds(viewport: Types.IVolumeViewport): string[] {
const actors = viewport.getActors?.() ?? [];
return actors
.filter(ac => ac.actor?.getClassName?.() === 'vtkVolume')
.map(ac => ac.referencedId)
.filter(Boolean) as string[];
}
function volumeIdPrefixesMatch(
existingIds: string[],
prefixLen: number,
targetIds: string[]
): boolean {
if (prefixLen > targetIds.length) {
return false;
}
for (let i = 0; i < prefixLen; i++) {
if (existingIds[i] !== targetIds[i]) {
return false;
}
}
return true;
}
/**
* Returns true when the viewport type matches a volume-based presentation (ORTHOGRAPHIC or VOLUME_3D).
*/
function viewportMatchesDesiredVolumePresentation(
viewport: Types.IViewport,
desiredViewportInfo: ViewportInfo
): boolean {
const desiredType = desiredViewportInfo.getViewportType();
if (viewport.type !== desiredType) {
return false;
}
return (
desiredType === csEnums.ViewportType.ORTHOGRAPHIC ||
desiredType === csEnums.ViewportType.VOLUME_3D
);
}
export const WITH_NAVIGATION = { withNavigation: true, withOrientation: false };
export const WITH_ORIENTATION = { withNavigation: true, withOrientation: true };
@@ -778,6 +820,19 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
/**
* Sets the image data for the given viewport.
*/
private async _setEcgViewport(
viewport: Types.IECGViewport,
viewportData: StackViewportData
): Promise<void> {
const [displaySet] = viewportData.data;
const imageId = displaySet.imageIds?.[0];
if (!imageId) {
console.error('[CornerstoneViewportService] ECG display set has no imageId');
return;
}
return viewport.setEcg(imageId);
}
private async _setOtherViewport(
viewport: Types.IStackViewport,
viewportData: StackViewportData,
@@ -856,28 +911,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
}
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
viewport.setProperties({ ...properties });
this.setPresentations(viewport.id, presentations, viewportInfo);
await viewport.setStack(imageIds, initialImageIndexToUse);
viewport.setProperties({ ...properties });
this.setPresentations(viewport.id, presentations, viewportInfo);
if (overlayProcessingResults?.length) {
overlayProcessingResults.forEach(overlayProcessingResult => {
if (overlayProcessingResult?.addOverlayFn) {
overlayProcessingResult.addOverlayFn();
}
});
}
await this._addOverlayRepresentations(overlayProcessingResults);
if (displayArea) {
viewport.setDisplayArea(displayArea);
}
if (rotation) {
viewport.setProperties({ rotation });
}
if (flipHorizontal) {
viewport.setCamera({ flipHorizontal: true });
}
});
if (displayArea) {
viewport.setDisplayArea(displayArea);
}
if (rotation) {
viewport.setProperties({ rotation });
}
if (flipHorizontal) {
viewport.setCamera({ flipHorizontal: true });
}
}
private _getInitialImageIndexForViewport(
@@ -1105,15 +1153,45 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
});
}
await viewport.setVolumes(volumeInputArray);
const baseVolumeInputs = filteredVolumeInputArray.map(({ volumeInput }) => volumeInput);
const nextBaseVolumeIds = baseVolumeInputs.map(v => v.volumeId);
const existingVolumeIds = getVolumeActorReferencedIds(viewport);
if (overlayProcessingResults?.length) {
overlayProcessingResults.forEach(({ addOverlayFn }) => {
if (addOverlayFn) {
addOverlayFn();
let skippedIdenticalBaseVolumes = false;
if (baseVolumeInputs.length) {
const singleBaseViewport = nextBaseVolumeIds.length === 1;
// Only skip setVolumes() when the viewport type already matches the desired OHIF type
// (ORTHOGRAPHIC / VOLUME_3D); otherwise a stack → MPR switch with the same volumeId
// would incorrectly skip rebuilding the viewport.
if (
singleBaseViewport &&
existingVolumeIds.length >= nextBaseVolumeIds.length &&
volumeIdPrefixesMatch(existingVolumeIds, nextBaseVolumeIds.length, nextBaseVolumeIds) &&
viewportMatchesDesiredVolumePresentation(viewport, viewportInfo)
) {
// Same primary volume already loaded (e.g. labelmap / extra actors after it) — avoid
// setVolumes(), which tears down all actors and blanks MPR during SEG hydrate.
skippedIdenticalBaseVolumes = true;
} else if (
existingVolumeIds.length &&
nextBaseVolumeIds.length > existingVolumeIds.length &&
volumeIdPrefixesMatch(existingVolumeIds, existingVolumeIds.length, nextBaseVolumeIds) &&
typeof viewport.addVolumes === 'function'
) {
const toAdd = baseVolumeInputs.slice(existingVolumeIds.length);
if (toAdd.length) {
await viewport.addVolumes(toAdd);
}
});
} else {
await viewport.setVolumes(baseVolumeInputs);
}
} else if (volumeInputArray.length) {
await viewport.setVolumes(volumeInputArray);
}
await this._addOverlayRepresentations(overlayProcessingResults);
viewport.render();
volumesProperties.forEach(({ properties, volumeId }) => {
@@ -1124,8 +1202,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
});
this.setPresentations(viewport.id, presentations);
// Presentations apply segmentation (hydrated labelmap etc.) after the render above — redraw so
// every orthographic/3D tile shows the updated scene (fixes MPR siblings blank after SEG hydrate).
viewport.render();
if (!presentations.positionPresentation) {
if (!presentations.positionPresentation && !skippedIdenticalBaseVolumes) {
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
if (imageIndex !== undefined) {
@@ -1179,7 +1260,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
private addOverlayRepresentationForDisplaySet(
displaySet: OhifTypes.DisplaySet,
viewport: Types.IViewport
) {
): Promise<void> {
const { segmentationService } = this.servicesManager.services;
const segmentationId = displaySet.displaySetInstanceUID;
@@ -1189,18 +1270,36 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
: csToolsEnums.SegmentationRepresentations.Contour;
const { predecessorImageId } = displaySet;
segmentationService.addSegmentationRepresentation(viewport.id, {
segmentationId,
predecessorImageId,
type: representationType,
config: {
blendMode:
viewport?.getBlendMode?.() === 1 ? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND : undefined,
},
});
const segmentationRepresentationPromise = segmentationService.addSegmentationRepresentation(
viewport.id,
{
segmentationId,
predecessorImageId,
type: representationType,
config: {
blendMode:
viewport?.getBlendMode?.() === 1
? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND
: undefined,
},
}
);
// store the segmentation presentation id in the viewport info
this.storePresentation({ viewportId: viewport.id });
return segmentationRepresentationPromise;
}
private async _addOverlayRepresentations(
overlayProcessingResults?: Array<{ addOverlayFn?: () => Promise<void> }>
): Promise<void> {
if (!overlayProcessingResults?.length) {
return;
}
for (const overlayProcessingResult of overlayProcessingResults) {
if (overlayProcessingResult?.addOverlayFn) {
await overlayProcessingResult.addOverlayFn();
}
}
}
// Todo: keepCamera is an interim solution until we have a better solution for
@@ -1257,6 +1356,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
);
}
if (viewport instanceof ECGViewport) {
return this._setEcgViewport(
viewport as unknown as Types.IECGViewport,
viewportData as StackViewportData
);
}
return this._setOtherViewport(
viewport,
viewportData as StackViewportData,
@@ -1308,8 +1414,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
return;
}
if (displaySet.frameOfReferenceUID) {
return displaySet.frameOfReferenceUID;
if (displaySet.FrameOfReferenceUID) {
return displaySet.FrameOfReferenceUID;
}
if (displaySet.Modality === 'SEG') {
@@ -135,16 +135,27 @@ const _getSegmentationPresentationId = ({
return;
}
const { displaySetService } = servicesManager.services;
const { displaySetInstanceUIDs, viewportOptions } = viewport;
// Match keys used by updateStoredSegmentationPresentation (referenced volume only).
// Including overlay UIDs (e.g. SEG) produced ids like "MR&SEG" while the store
// entry is under "MR", so hydrated segmentations never applied and viewports could mis-render.
const nonOverlayUIDs = displaySetInstanceUIDs.filter(uid => {
const ds = displaySetService.getDisplaySetByUID(uid);
return ds && !ds.isOverlayDisplaySet;
});
if (!nonOverlayUIDs.length) {
return;
}
let orientation = viewportOptions.orientation;
if (!orientation) {
// Calculate orientation from the viewport sample image
const displaySet = servicesManager.services.displaySetService.getDisplaySetByUID(
displaySetInstanceUIDs[0]
);
const sampleImage = displaySet.images?.[0];
const displaySet = displaySetService.getDisplaySetByUID(nonOverlayUIDs[0]);
const sampleImage = displaySet?.images?.[0];
const imageOrientationPatient = sampleImage?.ImageOrientationPatient;
orientation = getViewportOrientationFromImageOrientationPatient(imageOrientationPatient);
@@ -152,7 +163,7 @@ const _getSegmentationPresentationId = ({
const segmentationPresentationArr = [];
segmentationPresentationArr.push(...displaySetInstanceUIDs);
segmentationPresentationArr.push(...nonOverlayUIDs);
// Uncomment if unique indexing is needed
// addUniqueIndex(
@@ -5,6 +5,7 @@ import SegmentationServiceType from '../services/SegmentationService';
import SyncGroupServiceType from '../services/SyncGroupService';
import ToolGroupServiceType from '../services/ToolGroupService';
import ColorbarServiceType from '../services/ColorbarService';
import ViewedDataServiceType from '../services/ViewedDataService';
import * as cornerstone from '@cornerstonejs/core';
import * as cornerstoneTools from '@cornerstonejs/tools';
@@ -23,6 +24,7 @@ declare global {
export type SyncGroupService = SyncGroupServiceType;
export type ToolGroupService = ToolGroupServiceType;
export type ColorbarService = ColorbarServiceType;
export type ViewedDataService = ViewedDataServiceType;
export interface Services {
cornerstoneViewportService?: CornerstoneViewportServiceType;
@@ -31,6 +33,7 @@ declare global {
segmentationService?: SegmentationServiceType;
cornerstoneCacheService?: CornerstoneCacheServiceType;
colorbarService?: ColorbarServiceType;
viewedDataService?: ViewedDataServiceType;
}
export namespace Segmentation {
@@ -118,29 +118,50 @@ const CornerstoneViewportDownloadForm = ({
const downloadViewport = renderingEngine.getViewport(VIEWPORT_ID);
try {
// Capture current viewport state
// - properties: VOI, colormap, interpolation, etc.
// - viewPresentation: flip/rotate/zoom presentation state added for
// saving flip and rotation for capture
// - viewReference: image/volume reference
const properties = viewport.getProperties();
const viewPresentation = viewport.getViewPresentation?.();
const viewRef = viewport.getViewReference?.();
if (downloadViewport instanceof StackViewport) {
const imageId = viewport.getCurrentImageId();
const properties = viewport.getProperties();
await downloadViewport.setStack([imageId]);
downloadViewport.setProperties(properties);
} else if (downloadViewport instanceof BaseVolumeViewport) {
const volumeIds = viewport.getAllVolumeIds();
downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
await downloadViewport.setVolumes([{ volumeId: volumeIds[0] }]);
}
// Apply presentation state so captured image preserves flip/rotate
if (viewPresentation && downloadViewport.setViewPresentation) {
downloadViewport.setViewPresentation(viewPresentation);
}
// Apply viewport display properties
downloadViewport.setProperties(properties);
// Ensure correct image/volume reference
if (viewRef && downloadViewport.setViewReference) {
downloadViewport.setViewReference(viewRef);
}
downloadViewport.render();
// Re-apply segmentation overlays to the download viewport
if (segmentationRepresentations?.length) {
segmentationRepresentations.forEach(segRepresentation => {
const { segmentationId, colorLUTIndex, type } = segRepresentation;
if (type === Enums.SegmentationRepresentations.Labelmap) {
segmentation.addLabelmapRepresentationToViewportMap({
[downloadViewport.id]: [
{
segmentationId,
type: Enums.SegmentationRepresentations.Labelmap,
config: {
colorLUTOrIndex: colorLUTIndex,
},
config: { colorLUTOrIndex: colorLUTIndex },
},
],
});
@@ -152,9 +173,7 @@ const CornerstoneViewportDownloadForm = ({
{
segmentationId,
type: Enums.SegmentationRepresentations.Contour,
config: {
colorLUTOrIndex: colorLUTIndex,
},
config: { colorLUTOrIndex: colorLUTIndex },
},
],
});
@@ -230,6 +249,48 @@ const CornerstoneViewportDownloadForm = ({
downloadUrl(canvas.toDataURL(`image/${fileType}`, 1.0), { filename });
};
const handleCopyToClipboard = async () => {
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${VIEWPORT_ID}"]`
);
if (!divForDownloadViewport) {
console.debug('No viewport found for copy');
return;
}
try {
const canvas = await html2canvas(divForDownloadViewport as HTMLElement);
// Clipboard API only supports PNG format in most browsers
const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob(
blob => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Failed to create blob from canvas'));
}
},
'image/png',
1.0
);
});
// Copy to clipboard using the Clipboard API
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob,
}),
]);
console.log('Image copied to clipboard successfully');
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
throw error;
}
};
const ViewportDownloadFormNew = customizationService.getCustomization(
'ohif.captureViewportModal'
);
@@ -247,6 +308,7 @@ const CornerstoneViewportDownloadForm = ({
onEnableViewport={handleEnableViewport}
onDisableViewport={handleDisableViewport}
onDownload={handleDownload}
onCopyToClipboard={handleCopyToClipboard}
warningState={warningState}
/>
);
@@ -0,0 +1,217 @@
import { decodeInt16Multiplex, base64ToArrayBuffer, buildEcgModule } from './ecgMetadata';
// ---------------------------------------------------------------------------
// decodeInt16Multiplex
// ---------------------------------------------------------------------------
describe('decodeInt16Multiplex', () => {
it('demultiplexes 2 channels correctly', () => {
// Interleaved layout: [ch0_s0, ch1_s0, ch0_s1, ch1_s1, ch0_s2, ch1_s2]
const interleaved = new Int16Array([10, 20, 30, 40, 50, 60]);
const channels = decodeInt16Multiplex(interleaved.buffer, 2, 3);
expect(channels).toHaveLength(2);
expect(Array.from(channels[0])).toEqual([10, 30, 50]);
expect(Array.from(channels[1])).toEqual([20, 40, 60]);
});
it('handles a single channel', () => {
const data = new Int16Array([100, 200, 300]);
const channels = decodeInt16Multiplex(data.buffer, 1, 3);
expect(channels).toHaveLength(1);
expect(Array.from(channels[0])).toEqual([100, 200, 300]);
});
it('handles negative values (signed short)', () => {
const data = new Int16Array([-1000, -2000, -3000]);
const channels = decodeInt16Multiplex(data.buffer, 1, 3);
expect(Array.from(channels[0])).toEqual([-1000, -2000, -3000]);
});
it('returns empty arrays when numberOfChannels or numberOfSamples is 0', () => {
const emptyBuffer = new Int16Array([]).buffer;
expect(decodeInt16Multiplex(emptyBuffer, 0, 0)).toHaveLength(0);
expect(decodeInt16Multiplex(emptyBuffer, 0, 10)).toHaveLength(0);
});
it('produces Int16Array instances for each channel', () => {
const data = new Int16Array([1, 2]);
const channels = decodeInt16Multiplex(data.buffer, 2, 1);
expect(channels[0]).toBeInstanceOf(Int16Array);
expect(channels[1]).toBeInstanceOf(Int16Array);
});
});
// ---------------------------------------------------------------------------
// base64ToArrayBuffer
// ---------------------------------------------------------------------------
describe('base64ToArrayBuffer', () => {
it('decodes a simple base64 string to the correct bytes', () => {
// "ABC" in ASCII = [65, 66, 67]; base64 of that is 'QUJD'
const buffer = base64ToArrayBuffer('QUJD');
const bytes = new Uint8Array(buffer);
expect(bytes).toHaveLength(3);
expect(Array.from(bytes)).toEqual([65, 66, 67]);
});
it('round-trips an Int16Array through base64', () => {
const original = new Int16Array([500, -300, 0, 32767]);
const base64 = btoa(String.fromCharCode(...new Uint8Array(original.buffer)));
const recovered = new Int16Array(base64ToArrayBuffer(base64));
expect(Array.from(recovered)).toEqual(Array.from(original));
});
it('returns an ArrayBuffer', () => {
const result = base64ToArrayBuffer('AA=='); // single zero byte
expect(result).toBeInstanceOf(ArrayBuffer);
expect(result.byteLength).toBe(1);
});
});
// ---------------------------------------------------------------------------
// buildEcgModule
// ---------------------------------------------------------------------------
describe('buildEcgModule', () => {
const makeInstance = (overrides = {}) => ({
WaveformSequence: [
{
NumberOfWaveformChannels: 12,
NumberOfWaveformSamples: 5000,
SamplingFrequency: 500,
WaveformBitsAllocated: 16,
WaveformSampleInterpretation: 'SS',
MultiplexGroupLabel: '12 Lead ECG',
ChannelDefinitionSequence: [
{ ChannelSourceSequence: [{ CodeMeaning: 'Lead I' }] },
{ ChannelSourceSequence: [{ CodeMeaning: 'Lead II' }] },
],
WaveformData: { InlineBinary: btoa('\x00\x01') },
},
],
...overrides,
});
it('returns null when WaveformSequence is absent', () => {
expect(buildEcgModule({})).toBeNull();
expect(buildEcgModule({ WaveformSequence: [] })).toBeNull();
});
it('returns a valid ecgModule for a complete instance', () => {
const module = buildEcgModule(makeInstance());
expect(module).not.toBeNull();
expect(module!.numberOfWaveformChannels).toBe(12);
expect(module!.numberOfWaveformSamples).toBe(5000);
expect(module!.samplingFrequency).toBe(500);
expect(module!.waveformBitsAllocated).toBe(16);
expect(module!.waveformSampleInterpretation).toBe('SS');
expect(module!.multiplexGroupLabel).toBe('12 Lead ECG');
expect(module!.channelDefinitionSequence).toHaveLength(2);
expect(module!.channelDefinitionSequence[0].channelSourceSequence.codeMeaning).toBe('Lead I');
expect(typeof module!.waveformData.retrieveBulkData).toBe('function');
});
it('applies defaults for missing optional fields', () => {
const instance = {
WaveformSequence: [
{
// No explicit values — all defaults
WaveformData: { InlineBinary: btoa('\x00\x00') },
},
],
};
const module = buildEcgModule(instance);
expect(module!.numberOfWaveformChannels).toBe(0);
expect(module!.numberOfWaveformSamples).toBe(0);
expect(module!.samplingFrequency).toBe(1);
expect(module!.waveformBitsAllocated).toBe(16);
expect(module!.waveformSampleInterpretation).toBe('SS');
expect(module!.multiplexGroupLabel).toBe('');
expect(module!.channelDefinitionSequence).toHaveLength(0);
});
it('retrieveBulkData decodes InlineBinary data correctly', async () => {
const samples = new Int16Array([10, 20, 30]);
const base64 = btoa(String.fromCharCode(...new Uint8Array(samples.buffer)));
const instance = {
WaveformSequence: [
{
NumberOfWaveformChannels: 1,
NumberOfWaveformSamples: 3,
WaveformData: { InlineBinary: base64 },
},
],
};
const module = buildEcgModule(instance);
const channels = await module!.waveformData.retrieveBulkData();
expect(channels).toHaveLength(1);
expect(Array.from(channels[0])).toEqual([10, 20, 30]);
});
it('retrieveBulkData returns [] when WaveformData is absent', async () => {
const instance = {
WaveformSequence: [
{ NumberOfWaveformChannels: 1, NumberOfWaveformSamples: 1 },
],
};
const module = buildEcgModule(instance);
const channels = await module!.waveformData.retrieveBulkData();
expect(channels).toEqual([]);
});
it('retrieveBulkData fetches BulkDataURI with auth header when provided', async () => {
const samples = new Int16Array([1, 2]);
global.fetch = jest.fn().mockResolvedValue({
ok: true,
arrayBuffer: () => Promise.resolve(samples.buffer),
});
const userAuthenticationService = {
getAuthorizationHeader: () => ({ Authorization: 'Bearer token123' }),
};
const instance = {
WaveformSequence: [
{
NumberOfWaveformChannels: 1,
NumberOfWaveformSamples: 2,
WaveformData: { BulkDataURI: 'http://example.com/waveform' },
},
],
};
const module = buildEcgModule(instance, userAuthenticationService);
await module!.waveformData.retrieveBulkData();
expect(global.fetch).toHaveBeenCalledWith('http://example.com/waveform', {
headers: {
Accept: 'application/octet-stream',
Authorization: 'Bearer token123',
},
});
});
it('retrieveBulkData throws when BulkDataURI response is not ok', async () => {
global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 403 });
const instance = {
WaveformSequence: [
{
WaveformData: { BulkDataURI: 'http://example.com/waveform' },
},
],
};
const module = buildEcgModule(instance);
await expect(module!.waveformData.retrieveBulkData()).rejects.toThrow('403');
});
});
@@ -0,0 +1,129 @@
/**
* Decode a multiplexed Int16 buffer into per-channel arrays.
* Layout: sample0ch0, sample0ch1 ... sample0chN, sample1ch0, …
* Note: DICOM ECG data is canonically SS (signed short). The sampleInterpretation
* field is forwarded to ECGViewport for its own use; the raw buffer is always
* treated as Int16 because Cornerstone ECGViewport expects Int16Array[].
*/
export function decodeInt16Multiplex(
buffer: ArrayBuffer,
numberOfChannels: number,
numberOfSamples: number
): Int16Array[] {
const src = new Int16Array(buffer);
const channels: Int16Array[] = [];
for (let ch = 0; ch < numberOfChannels; ch++) {
const out = new Int16Array(numberOfSamples);
for (let s = 0; s < numberOfSamples; s++) {
out[s] = src[s * numberOfChannels + ch];
}
channels.push(out);
}
return channels;
}
/**
* Decode a base64 InlineBinary string into a raw ArrayBuffer.
*/
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
const binaryStr = atob(base64);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes.buffer;
}
export type EcgModule = {
numberOfWaveformChannels: number;
numberOfWaveformSamples: number;
samplingFrequency: number;
waveformBitsAllocated: number;
waveformSampleInterpretation: string;
multiplexGroupLabel: string;
channelDefinitionSequence: Array<{ channelSourceSequence: { codeMeaning: string } }>;
waveformData: { retrieveBulkData: () => Promise<Int16Array[]> };
};
/**
* Parse the naturalized DICOM instance's WaveformSequence and build the ecgModule
* that Cornerstone's ECGViewport.setEcg() expects via
* metaData.get(MetadataModules.ECG, imageId).
*
* Returns null if the instance has no WaveformSequence.
*/
export function buildEcgModule(
instance: any,
userAuthenticationService?: any
): EcgModule | null {
const waveformGroups = instance?.WaveformSequence;
if (!waveformGroups?.length) {
return null;
}
// Use the first (and typically only) multiplex group
const group = waveformGroups[0];
const numberOfChannels = group.NumberOfWaveformChannels ?? 0;
const numberOfSamples = group.NumberOfWaveformSamples ?? 0;
const samplingFrequency = group.SamplingFrequency ?? 1;
const bitsAllocated = group.WaveformBitsAllocated ?? 16;
const sampleInterpretation = group.WaveformSampleInterpretation ?? 'SS';
const multiplexGroupLabel = group.MultiplexGroupLabel ?? '';
const channelDefinitionSequence = (group.ChannelDefinitionSequence ?? []).map(ch => ({
channelSourceSequence: {
codeMeaning:
ch?.ChannelSourceSequence?.[0]?.CodeMeaning ??
ch?.ChannelSourceSequence?.[0]?.codeMeaning ??
'',
},
}));
const retrieveBulkData = async (): Promise<Int16Array[]> => {
const waveformData = group.WaveformData;
if (!waveformData) {
console.warn('[ECGViewport] No WaveformData found on instance');
return [];
}
let buffer: ArrayBuffer;
if (waveformData.InlineBinary) {
buffer = base64ToArrayBuffer(waveformData.InlineBinary);
} else if (waveformData.BulkDataURI) {
const headers: Record<string, string> = {
Accept: 'application/octet-stream',
};
const authHeader = userAuthenticationService?.getAuthorizationHeader?.();
if (authHeader) {
Object.assign(headers, authHeader);
}
const response = await fetch(waveformData.BulkDataURI, { headers });
if (!response.ok) {
throw new Error(
`[ECGViewport] Failed to fetch waveform BulkDataURI: ${response.status}`
);
}
buffer = await response.arrayBuffer();
} else {
console.warn('[ECGViewport] WaveformData has no InlineBinary or BulkDataURI');
return [];
}
return decodeInt16Multiplex(buffer, numberOfChannels, numberOfSamples);
};
return {
numberOfWaveformChannels: numberOfChannels,
numberOfWaveformSamples: numberOfSamples,
samplingFrequency,
waveformBitsAllocated: bitsAllocated,
waveformSampleInterpretation: sampleInterpretation,
multiplexGroupLabel,
channelDefinitionSequence,
waveformData: { retrieveBulkData },
};
}
@@ -8,6 +8,9 @@ jest.mock('@cornerstonejs/core', () => ({
SAGITTAL: 'sagittal',
CORONAL: 'coronal',
ACQUISITION: 'acquisition',
AXIAL_REFORMAT: 'axial_reformat',
SAGITTAL_REFORMAT: 'sagittal_reformat',
CORONAL_REFORMAT: 'coronal_reformat',
},
},
}));
@@ -33,6 +36,21 @@ describe('getCornerstoneOrientation', () => {
expect(result).toBe(Enums.OrientationAxis.CORONAL);
});
it('should return AXIAL_REFORMAT when orientation is axial_reformat', () => {
const result = getCornerstoneOrientation('axial_reformat');
expect(result).toBe(Enums.OrientationAxis.AXIAL_REFORMAT);
});
it('should return SAGITTAL_REFORMAT when orientation is sagittal_reformat', () => {
const result = getCornerstoneOrientation('sagittal_reformat');
expect(result).toBe(Enums.OrientationAxis.SAGITTAL_REFORMAT);
});
it('should return CORONAL_REFORMAT when orientation is coronal_reformat', () => {
const result = getCornerstoneOrientation('coronal_reformat');
expect(result).toBe(Enums.OrientationAxis.CORONAL_REFORMAT);
});
it('should return ACQUISITION for unknown orientation', () => {
const result = getCornerstoneOrientation('unknown');
expect(result).toBe(Enums.OrientationAxis.ACQUISITION);
@@ -3,16 +3,25 @@ import { Enums } from '@cornerstonejs/core';
const AXIAL = 'axial';
const SAGITTAL = 'sagittal';
const CORONAL = 'coronal';
const AXIAL_REFORMAT = 'axial_reformat';
const SAGITTAL_REFORMAT = 'sagittal_reformat';
const CORONAL_REFORMAT = 'coronal_reformat';
export default function getCornerstoneOrientation(orientation: string): Enums.OrientationAxis {
if (orientation) {
switch (orientation.toLowerCase()) {
case AXIAL:
return Enums.OrientationAxis.AXIAL;
case AXIAL_REFORMAT:
return Enums.OrientationAxis.AXIAL_REFORMAT;
case SAGITTAL:
return Enums.OrientationAxis.SAGITTAL;
case SAGITTAL_REFORMAT:
return Enums.OrientationAxis.SAGITTAL_REFORMAT;
case CORONAL:
return Enums.OrientationAxis.CORONAL;
case CORONAL_REFORMAT:
return Enums.OrientationAxis.CORONAL_REFORMAT;
default:
return Enums.OrientationAxis.ACQUISITION;
}
@@ -10,6 +10,7 @@ jest.mock('@cornerstonejs/core', () => ({
WHOLE_SLIDE: 'wholeslide',
ORTHOGRAPHIC: 'orthographic',
VOLUME_3D: 'volume3d',
ECG: 'ecg',
},
},
}));
@@ -50,9 +51,14 @@ describe('getCornerstoneViewportType', () => {
expect(result).toBe(Enums.ViewportType.VOLUME_3D);
});
it('should return ECG when viewportType is ecg', () => {
const result = getCornerstoneViewportType('ecg');
expect(result).toBe(Enums.ViewportType.ECG);
});
it('should throw error for invalid viewport type', () => {
expect(() => getCornerstoneViewportType('invalid')).toThrow(
'Invalid viewport type: invalid. Valid types are: stack, volume, video, wholeslide'
'Invalid viewport type: invalid. Valid types are: stack, volume, video, wholeslide, ecg'
);
});
@@ -7,6 +7,7 @@ const ORTHOGRAPHIC = 'orthographic';
const VOLUME_3D = 'volume3d';
const VIDEO = 'video';
const WHOLESLIDE = 'wholeslide';
const ECG = 'ecg';
export default function getCornerstoneViewportType(
viewportType: string,
@@ -25,6 +26,10 @@ export default function getCornerstoneViewportType(
return Enums.ViewportType.WHOLE_SLIDE;
}
if (lowerViewportType === ECG) {
return Enums.ViewportType.ECG;
}
if (lowerViewportType === VOLUME || lowerViewportType === ORTHOGRAPHIC) {
return Enums.ViewportType.ORTHOGRAPHIC;
}
@@ -34,6 +39,6 @@ export default function getCornerstoneViewportType(
}
throw new Error(
`Invalid viewport type: ${viewportType}. Valid types are: stack, volume, video, wholeslide`
`Invalid viewport type: ${viewportType}. Valid types are: stack, volume, video, wholeslide, ecg`
);
}
@@ -0,0 +1,22 @@
/**
* Resolves the data ID (e.g. volumeId) for a viewport and display set.
* For viewports with multiple volumes/actors, returns the id that matches the display set; otherwise undefined.
* Use this to call viewport.getProperties(dataId) in a viewport-type-agnostic way.
*
* @param viewport - Viewport instance (stack, volume, or future types with optional getAllVolumeIds)
* @param displaySetInstanceUID - Display set instance UID to match
* @returns volumeId (or equivalent) for multi-actor viewports, undefined for single-actor
*/
export function getDataIdForViewport(
viewport: unknown,
displaySetInstanceUID: string
): string | undefined {
const vp = viewport as { getAllVolumeIds?: () => string[] };
if (typeof vp.getAllVolumeIds !== 'function') {
return undefined;
}
const volumeIds = vp.getAllVolumeIds() || [];
return volumeIds.length > 0
? volumeIds.find(id => id.includes(displaySetInstanceUID)) ?? undefined
: undefined;
}
@@ -262,6 +262,57 @@ describe('getUpdatedViewportsForSegmentation', () => {
expect(result).toEqual(mockUpdatedViewports);
});
it('should merge all grid viewports that reference the volume after hydration (e.g. 3D four-up)', () => {
const volumeUid = 'volume-1';
const viewports = new Map([
[
'vp-axial',
{
viewportId: 'vp-axial',
viewportOptions: { viewportId: 'vp-axial' },
displaySetInstanceUIDs: [volumeUid],
},
],
[
'vp-sagittal',
{
viewportId: 'vp-sagittal',
viewportOptions: { viewportId: 'vp-sagittal' },
displaySetInstanceUIDs: [volumeUid],
},
],
[
'vp-other-study',
{
viewportId: 'vp-other-study',
viewportOptions: { viewportId: 'vp-other-study' },
displaySetInstanceUIDs: ['other-volume'],
},
],
]);
mockViewportGridService.getState.mockReturnValue({
isHangingProtocolLayout: true,
viewports,
activeViewportId: 'vp-axial',
});
mockHangingProtocolService.getViewportsRequireUpdate.mockReturnValue([
{ viewportId: 'vp-axial', displaySetInstanceUIDs: [volumeUid] },
]);
const result = getUpdatedViewportsForSegmentation({
viewportId: 'vp-axial',
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
displaySetInstanceUIDs: [volumeUid],
});
expect(result).toEqual([
{ viewportId: 'vp-axial', displaySetInstanceUIDs: [volumeUid] },
{ viewportId: 'vp-sagittal', displaySetInstanceUIDs: [volumeUid] },
]);
});
it('should handle complex viewport structure', () => {
const complexViewport = {
viewportOptions: {
@@ -1,3 +1,63 @@
import type { Types } from '@ohif/core';
/**
* After SEG hydration we must refresh every viewport that shows the referenced volume so
* presentations (including segmentation) apply to all MPR/3D tiles. Hanging-protocol matching
* can return only the active viewport when protocol definitions omit viewportId (e.g. 3D four-up)
* or when layout state diverges from the protocol; this merges in all grid panes that already
* list that volume in `displaySetInstanceUIDs`.
*
* Only exact displaySetInstanceUID matches are used (no Frame-of-Reference inference): sibling
* MPR planes must already share the same referenced volume UID in grid state, or HP matching
* must list them; otherwise forcing a different UID onto a volume viewport can leave it blank.
*/
function mergeVolumeSharingViewports(
hangingProtocolUpdates: Types.HangingProtocol.ViewportUpdate[] | null | undefined,
volumeUid: string | undefined,
viewports: AppTypes.ViewportGrid.GridViewports
): Types.HangingProtocol.ViewportUpdate[] {
if (!volumeUid) {
return (hangingProtocolUpdates ?? []) as Types.HangingProtocol.ViewportUpdate[];
}
const byId = new Map<string, Types.HangingProtocol.ViewportUpdate>();
const add = (viewportId: string, uids: string[]) => {
if (!viewportId) {
return;
}
byId.set(viewportId, { viewportId, displaySetInstanceUIDs: uids });
};
if (Array.isArray(hangingProtocolUpdates)) {
for (const entry of hangingProtocolUpdates) {
const vid = entry.viewportId ?? entry.viewportOptions?.viewportId;
if (vid) {
add(vid, entry.displaySetInstanceUIDs?.length ? entry.displaySetInstanceUIDs : [volumeUid]);
}
}
}
viewports.forEach(vp => {
const uids = vp.displaySetInstanceUIDs || [];
if (uids.includes(volumeUid)) {
add(vp.viewportId, [volumeUid]);
}
});
const merged = Array.from(byId.values());
if (
merged.length === 0 &&
Array.isArray(hangingProtocolUpdates) &&
hangingProtocolUpdates.length > 0
) {
return hangingProtocolUpdates;
}
return merged;
}
function getUpdatedViewportsForSegmentation({
viewportId,
servicesManager,
@@ -5,7 +65,7 @@ function getUpdatedViewportsForSegmentation({
}: withAppTypes) {
const { hangingProtocolService, viewportGridService } = servicesManager.services;
const { isHangingProtocolLayout } = viewportGridService.getState();
const { isHangingProtocolLayout, viewports } = viewportGridService.getState();
const viewport = getTargetViewport({ viewportId, viewportGridService });
const targetViewportId = viewport.viewportOptions.viewportId;
@@ -16,7 +76,11 @@ function getUpdatedViewportsForSegmentation({
isHangingProtocolLayout
);
return updatedViewports;
if (updatedViewports == null) {
return updatedViewports;
}
return mergeVolumeSharingViewports(updatedViewports, displaySetInstanceUIDs[0], viewports);
}
const getTargetViewport = ({ viewportId, viewportGridService }) => {
@@ -96,7 +96,7 @@ function getMappedAnnotations(annotation, displaySetService) {
const { cachedStats } = data;
const { referencedImageId } = metadata;
if( !cachedStats ) {
if (!cachedStats) {
return;
}
@@ -160,8 +160,8 @@ function getColumnValueReport(annotation, customizationService) {
const { metadata, data } = annotation;
const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`];
report.forEach(({ name, value }) => {
columns.push(name);
report.forEach(({ displayName, value }) => {
columns.push(displayName);
stats[value] ? values.push(stats[value]) : values.push('not available');
});
@@ -158,8 +158,8 @@ function getColumnValueReport(annotation, customizationService) {
const { metadata, data } = annotation;
const stats = data.cachedStats[`imageId:${metadata.referencedImageId}`];
report.forEach(({ name, value }) => {
columns.push(name);
report.forEach(({ displayName, value }) => {
columns.push(displayName);
stats[value] ? values.push(stats[value]) : values.push('not available');
});
@@ -11,7 +11,7 @@ describe('promptHydrationDialog', () => {
};
const mockExtensionManager = {
_appConfig: {
appConfig: {
measurementTrackingMode: 'standard',
disableConfirmationPrompts: false,
},
@@ -57,7 +57,7 @@ describe('promptHydrationDialog', () => {
});
it('should return HYDRATE response when disableConfirmationPrompts is true for non-SR types', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
const result = await promptHydrationDialog(defaultParameters);
@@ -71,7 +71,7 @@ describe('promptHydrationDialog', () => {
});
it('should show dialog when disableConfirmationPrompts is false for non-SR types', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
@@ -107,7 +107,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle CANCEL response', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
@@ -120,7 +120,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle onOutsideClick', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
@@ -133,7 +133,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle onKeyPress Enter', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
@@ -146,7 +146,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle onKeyPress non-Enter key', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog(defaultParameters);
@@ -163,7 +163,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle SEG type hydration with setTimeout', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
@@ -178,7 +178,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle RTSTRUCT type hydration', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
const result = await promptHydrationDialog({
...defaultParameters,
@@ -194,7 +194,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle SR type hydration when standardMode is true', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockExtensionManager.appConfig.measurementTrackingMode = 'standard';
const mockHydrationResult = {
StudyInstanceUID: 'test-study-uid',
SeriesInstanceUIDs: ['series-1', 'series-2'],
@@ -222,7 +222,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle SR type hydration when standardMode is false', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'other';
mockExtensionManager.appConfig.measurementTrackingMode = 'other';
const result = await promptHydrationDialog({
...defaultParameters,
@@ -241,7 +241,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle SR type with CANCEL response', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockExtensionManager.appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
@@ -262,7 +262,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle undefined preHydrateCallbacks', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
@@ -273,7 +273,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle empty preHydrateCallbacks array', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
@@ -285,7 +285,7 @@ describe('promptHydrationDialog', () => {
it('should execute multiple preHydrateCallbacks', async () => {
const mockPreHydrateCallback2 = jest.fn();
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
await promptHydrationDialog({
...defaultParameters,
@@ -297,7 +297,7 @@ describe('promptHydrationDialog', () => {
});
it('should get correct customization message key for SEG type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -314,7 +314,7 @@ describe('promptHydrationDialog', () => {
});
it('should get correct customization message key for RTSTRUCT type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -331,7 +331,7 @@ describe('promptHydrationDialog', () => {
});
it('should get correct customization message key for SR type', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockExtensionManager.appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
@@ -348,7 +348,7 @@ describe('promptHydrationDialog', () => {
});
it('should get default customization message key for unknown type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -365,7 +365,7 @@ describe('promptHydrationDialog', () => {
});
it('should get correct dialog id for SEG type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -384,7 +384,7 @@ describe('promptHydrationDialog', () => {
});
it('should get correct dialog id for SR type', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockExtensionManager.appConfig.measurementTrackingMode = 'standard';
const promise = promptHydrationDialog({
...defaultParameters,
@@ -403,7 +403,7 @@ describe('promptHydrationDialog', () => {
});
it('should get default dialog id for unknown type', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -422,7 +422,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle hydrateCallback returning false for SEG', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
mockHydrateCallback.mockResolvedValue(false);
const result = await promptHydrationDialog({
@@ -434,7 +434,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle hydrateCallback returning false for RTSTRUCT', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = true;
mockExtensionManager.appConfig.disableConfirmationPrompts = true;
mockHydrateCallback.mockResolvedValue(false);
const result = await promptHydrationDialog({
@@ -446,7 +446,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle SR hydration with null result', async () => {
mockExtensionManager._appConfig.measurementTrackingMode = 'standard';
mockExtensionManager.appConfig.measurementTrackingMode = 'standard';
mockHydrateCallback.mockResolvedValue(null);
const promise = promptHydrationDialog({
@@ -470,7 +470,7 @@ describe('promptHydrationDialog', () => {
});
it('should handle RTSTRUCT type in getDialogId', async () => {
mockExtensionManager._appConfig.disableConfirmationPrompts = false;
mockExtensionManager.appConfig.disableConfirmationPrompts = false;
const promise = promptHydrationDialog({
...defaultParameters,
@@ -70,7 +70,7 @@ function promptHydrationDialog({
}: HydrationDialogProps): Promise<boolean | HydrationSRResult> {
const { uiViewportDialogService, customizationService } = servicesManager.services;
const extensionManager = servicesManager._extensionManager;
const appConfig = extensionManager._appConfig;
const appConfig = extensionManager.appConfig;
// Todo: make this use enum from the extension, we should move the enum
const standardMode = appConfig?.measurementTrackingMode === 'standard';
@@ -94,22 +94,30 @@ function promptHydrationDialog({
if (type === HydrationType.SEG) {
// SEG needs setTimeout
window.setTimeout(async () => {
const isHydrated = await hydrateCallback({
segDisplaySet: displaySet,
viewportId,
});
resolve(isHydrated);
try {
const isHydrated = await hydrateCallback({
segDisplaySet: displaySet,
viewportId,
});
resolve(isHydrated);
} catch (error) {
reject(error);
}
}, 0);
} else if (type === HydrationType.RTSTRUCT) {
// RT hydration
const isHydrated = await hydrateCallback({
rtDisplaySet: displaySet,
viewportId,
servicesManager,
});
resolve(isHydrated);
window.setTimeout(async () => {
try {
const isHydrated = await hydrateCallback({
rtDisplaySet: displaySet,
viewportId,
servicesManager,
});
resolve(isHydrated);
} catch (error) {
reject(error);
}
}, 0);
} else if (type === HydrationType.SR) {
// SR has a different result structure
const hydrationResult = await hydrateCallback(displaySet);
@@ -15,6 +15,7 @@ describe('setUpSegmentationEventHandlers', () => {
const mockSegmentationService = {
EVENTS: {
SEGMENTATION_ADDED: 'SEGMENTATION_ADDED',
SEGMENTATION_REMOVED: 'SEGMENTATION_REMOVED',
},
subscribe: jest.fn(),
getSegmentation: jest.fn(),
@@ -40,6 +41,7 @@ describe('setUpSegmentationEventHandlers', () => {
const mockUnsubscribeDataModified = jest.fn();
const mockUnsubscribeModified = jest.fn();
const mockUnsubscribeCreated = jest.fn();
const mockUnsubscribeRemoved = jest.fn();
const mockUnsubscribeSelectedSegmentationsForViewportEvents = [jest.fn(), jest.fn()];
const defaultParameters = {
@@ -59,8 +61,14 @@ describe('setUpSegmentationEventHandlers', () => {
unsubscribeSelectedSegmentationsForViewportEvents:
mockUnsubscribeSelectedSegmentationsForViewportEvents,
});
mockSegmentationService.subscribe.mockReturnValue({
unsubscribe: mockUnsubscribeCreated,
mockSegmentationService.subscribe.mockImplementation((eventName: string) => {
if (eventName === mockSegmentationService.EVENTS.SEGMENTATION_ADDED) {
return { unsubscribe: mockUnsubscribeCreated };
}
if (eventName === mockSegmentationService.EVENTS.SEGMENTATION_REMOVED) {
return { unsubscribe: mockUnsubscribeRemoved };
}
return { unsubscribe: jest.fn() };
});
});
@@ -99,6 +107,7 @@ describe('setUpSegmentationEventHandlers', () => {
mockUnsubscribeDataModified,
mockUnsubscribeModified,
mockUnsubscribeCreated,
mockUnsubscribeRemoved,
...mockUnsubscribeSelectedSegmentationsForViewportEvents,
],
});
@@ -358,5 +367,6 @@ describe('setUpSegmentationEventHandlers', () => {
expect(mockUnsubscribeDataModified).toHaveBeenCalled();
expect(mockUnsubscribeModified).toHaveBeenCalled();
expect(mockUnsubscribeCreated).toHaveBeenCalled();
expect(mockUnsubscribeRemoved).toHaveBeenCalled();
});
});
@@ -7,7 +7,8 @@ import {
} from './segmentationHandlers';
export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManager }) => {
const { segmentationService, customizationService, displaySetService } = servicesManager.services;
const { segmentationService, customizationService, displaySetService, viewportGridService } =
servicesManager.services;
const { unsubscribe: unsubscribeSegmentationDataModifiedHandler } =
setupSegmentationDataModifiedHandler({
@@ -57,6 +58,36 @@ export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManage
}
);
const { unsubscribe: unsubscribeSegmentationRemoved } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_REMOVED,
({ segmentationId }) => {
const displaySet = displaySetService.getDisplaySetByUID(segmentationId);
// Remove the display set layer from all viewports that have it
if (displaySet) {
const state = viewportGridService.getState();
const viewports = state.viewports;
// Find all viewports that contain this segmentation's display set as a layer
for (const [viewportId, viewport] of viewports.entries()) {
const displaySetInstanceUIDs = viewport.displaySetInstanceUIDs || [];
if (displaySetInstanceUIDs.includes(segmentationId)) {
// Remove the display set layer from this viewport
commandsManager.runCommand('removeDisplaySetLayer', {
viewportId,
displaySetInstanceUID: segmentationId,
});
}
}
// Delete the display set from the service if it was made in client
if (displaySet.madeInClient) {
displaySetService.deleteDisplaySet(segmentationId);
}
}
}
);
const { unsubscribeSelectedSegmentationsForViewportEvents } =
setUpSelectedSegmentationsForViewportHandler({
segmentationService,
@@ -66,6 +97,7 @@ export const setUpSegmentationEventHandlers = ({ servicesManager, commandsManage
unsubscribeSegmentationDataModifiedHandler,
unsubscribeSegmentationModifiedHandler,
unsubscribeSegmentationCreated,
unsubscribeSegmentationRemoved,
...unsubscribeSelectedSegmentationsForViewportEvents,
];
+552
View File
@@ -3,6 +3,558 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.13.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.77...v3.13.0-beta.78) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.76...v3.13.0-beta.77) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.75...v3.13.0-beta.76) (2026-05-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.74...v3.13.0-beta.75) (2026-05-18)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.73...v3.13.0-beta.74) (2026-05-15)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.72...v3.13.0-beta.73) (2026-05-13)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.71...v3.13.0-beta.72) (2026-05-12)
### Bug Fixes
* **measurement-tracking:** restore tracked state on undo after Delete all ([#5994](https://github.com/OHIF/Viewers/issues/5994)) ([397aa4d](https://github.com/OHIF/Viewers/commit/397aa4d0e361e95dcbd83e0557a9cd84c0b8440a))
# [3.13.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.70...v3.13.0-beta.71) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.69...v3.13.0-beta.70) (2026-05-07)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.68...v3.13.0-beta.69) (2026-05-06)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.67...v3.13.0-beta.68) (2026-05-05)
### Bug Fixes
* **ToolGroupService:** Centralize tool binding persistence and provide API to add and remove persisted bindings. ([#5989](https://github.com/OHIF/Viewers/issues/5989)) ([979d619](https://github.com/OHIF/Viewers/commit/979d619f7ccb426beb75d59721b01c98175d5241))
# [3.13.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.66...v3.13.0-beta.67) (2026-04-30)
### Features
* **ui-next:** Adds toggle state for ToolButton and Crosshair example ([#5914](https://github.com/OHIF/Viewers/issues/5914)) ([691e267](https://github.com/OHIF/Viewers/commit/691e26731f4f3b3957fe81fc051b09c972696cf6))
# [3.13.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.65...v3.13.0-beta.66) (2026-04-29)
### Bug Fixes
* **config url:** Hardening fetch options. ([#5985](https://github.com/OHIF/Viewers/issues/5985)) ([468e573](https://github.com/OHIF/Viewers/commit/468e5734a9ab02516430cc0dab5d7f2106dea950))
# [3.13.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.64...v3.13.0-beta.65) (2026-04-29)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.63...v3.13.0-beta.64) (2026-04-27)
### Bug Fixes
* **seg:** prevent segmentations from spreading to all viewports before hydration confirmation in 3D four-up ([#5967](https://github.com/OHIF/Viewers/issues/5967)) ([f8ccf9f](https://github.com/OHIF/Viewers/commit/f8ccf9ff2ea9ab7c38bd427514a8ae87902822a3))
# [3.13.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.62...v3.13.0-beta.63) (2026-04-27)
### Bug Fixes
* **security:** refine dynamic config URL trust policy and document trusted-origin behavior ([#5973](https://github.com/OHIF/Viewers/issues/5973)) ([f3cca21](https://github.com/OHIF/Viewers/commit/f3cca21e49729fbf4bab85aa1de64b9dcfebd0ce))
# [3.13.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.61...v3.13.0-beta.62) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.60...v3.13.0-beta.61) (2026-04-23)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.59...v3.13.0-beta.60) (2026-04-21)
### Bug Fixes
* **configuration:** Harden dynamic datasource URL trust boundaries and credential handling. ([#5963](https://github.com/OHIF/Viewers/issues/5963)) ([eede569](https://github.com/OHIF/Viewers/commit/eede569a88ed6a3177bbdea8552dfb93ca13cac5))
# [3.13.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.58...v3.13.0-beta.59) (2026-04-16)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.57...v3.13.0-beta.58) (2026-04-14)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.56...v3.13.0-beta.57) (2026-04-13)
### Bug Fixes
* Enhance multiframe instance handling and improve instance valida… ([#5956](https://github.com/OHIF/Viewers/issues/5956)) ([f5a66b9](https://github.com/OHIF/Viewers/commit/f5a66b9eea236cd3291eac857208d7465516c89a))
# [3.13.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.55...v3.13.0-beta.56) (2026-04-10)
### Bug Fixes
* A couple of changes to enable cs3d integration build ([#5944](https://github.com/OHIF/Viewers/issues/5944)) ([f6bbd5c](https://github.com/OHIF/Viewers/commit/f6bbd5c779e4692dc47c93327a890661b8dcc174))
# [3.13.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.54...v3.13.0-beta.55) (2026-04-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.53...v3.13.0-beta.54) (2026-04-08)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.52...v3.13.0-beta.53) (2026-04-06)
### Reverts
* rename DisplaySet.frameOfReferenceUID back to FrameOfReferenceUID ([#5943](https://github.com/OHIF/Viewers/issues/5943)) ([0e933c2](https://github.com/OHIF/Viewers/commit/0e933c256e07b7cda35ed2ba3cfb1ab35d895d57))
# [3.13.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.51...v3.13.0-beta.52) (2026-04-06)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.50...v3.13.0-beta.51) (2026-04-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.49...v3.13.0-beta.50) (2026-04-02)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.48...v3.13.0-beta.49) (2026-04-02)
### Bug Fixes
* **segmentation:** restrict overlay segmentation menu to same frame of reference as viewport background display set ([#5900](https://github.com/OHIF/Viewers/issues/5900)) ([b9029ef](https://github.com/OHIF/Viewers/commit/b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8))
# [3.13.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.47...v3.13.0-beta.48) (2026-04-01)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.46...v3.13.0-beta.47) (2026-03-28)
### Bug Fixes
* prevent black viewport when navigating series with client-created segmentation ([#5919](https://github.com/OHIF/Viewers/issues/5919)) ([e66fb5a](https://github.com/OHIF/Viewers/commit/e66fb5a2b8c93a60ea20d315f73def7a02ea205f))
# [3.13.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.45...v3.13.0-beta.46) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.44...v3.13.0-beta.45) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.43...v3.13.0-beta.44) (2026-03-27)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.42...v3.13.0-beta.43) (2026-03-17)
### Bug Fixes
* **SR:** Added support for spline and live wire SR items. ([#5870](https://github.com/OHIF/Viewers/issues/5870)) ([1d4802c](https://github.com/OHIF/Viewers/commit/1d4802c2a38dd75dca7afca87913e100453c9135))
# [3.13.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.41...v3.13.0-beta.42) (2026-03-17)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.40...v3.13.0-beta.41) (2026-03-16)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.39...v3.13.0-beta.40) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.38...v3.13.0-beta.39) (2026-03-13)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.37...v3.13.0-beta.38) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.36...v3.13.0-beta.37) (2026-03-12)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.35...v3.13.0-beta.36) (2026-03-11)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.34...v3.13.0-beta.35) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.33...v3.13.0-beta.34) (2026-03-09)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.32...v3.13.0-beta.33) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.31...v3.13.0-beta.32) (2026-03-05)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.30...v3.13.0-beta.31) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.29...v3.13.0-beta.30) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.28...v3.13.0-beta.29) (2026-03-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.27...v3.13.0-beta.28) (2026-03-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.26...v3.13.0-beta.27) (2026-03-02)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.25...v3.13.0-beta.26) (2026-02-28)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.24...v3.13.0-beta.25) (2026-02-26)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.23...v3.13.0-beta.24) (2026-02-24)
### Bug Fixes
* prevent viewer crash when opening DICOM Tag Browser from empty viewport ([#5827](https://github.com/OHIF/Viewers/issues/5827)) ([d11d6d4](https://github.com/OHIF/Viewers/commit/d11d6d401bc92a3870ec1089d97235b01a6b27c3))
# [3.13.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.22...v3.13.0-beta.23) (2026-02-23)
### Bug Fixes
* palette color 8 encoded 16 ([#5823](https://github.com/OHIF/Viewers/issues/5823)) ([eeca1f7](https://github.com/OHIF/Viewers/commit/eeca1f7a7b529d1700f5d281a6fd98074547ffe9)), closes [#5813](https://github.com/OHIF/Viewers/issues/5813)
# [3.13.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.21...v3.13.0-beta.22) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.20...v3.13.0-beta.21) (2026-02-23)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.19...v3.13.0-beta.20) (2026-02-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.18...v3.13.0-beta.19) (2026-02-20)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.17...v3.13.0-beta.18) (2026-02-19)
### Bug Fixes
* **segmentation overlay:** update viewport ds list upon seg delete - OHIF-2425 ([#5729](https://github.com/OHIF/Viewers/issues/5729)) ([b353541](https://github.com/OHIF/Viewers/commit/b35354120a01d89b27844f967c32c052bba8b096))
# [3.13.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.16...v3.13.0-beta.17) (2026-02-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.15...v3.13.0-beta.16) (2026-02-18)
**Note:** Version bump only for package @ohif/extension-default
# [3.13.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.13.0-beta.14...v3.13.0-beta.15) (2026-02-17)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-default",
"version": "3.13.0-beta.15",
"version": "3.13.0-beta.78",
"description": "Common/default features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@@ -34,8 +34,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.13.0-beta.15",
"@ohif/i18n": "3.13.0-beta.15",
"@ohif/core": "3.13.0-beta.78",
"@ohif/i18n": "3.13.0-beta.78",
"dcmjs": "0.49.4",
"dicomweb-client": "0.10.4",
"prop-types": "15.8.1",
@@ -1,5 +1,3 @@
import { DicomMetadataStore } from '@ohif/core';
/**
*
* @param {*} servicesManager
@@ -7,7 +5,8 @@ import { DicomMetadataStore } from '@ohif/core';
async function createReportAsync({
servicesManager,
getReport,
reportType = 'measurement',
reportType = 'Measurements',
successMessage,
}: withAppTypes) {
const { displaySetService, uiNotificationService, uiDialogService } = servicesManager.services;
@@ -18,18 +17,15 @@ async function createReportAsync({
return;
}
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
DicomMetadataStore.addInstances([naturalizedReport], true);
// addInstances is called by the store command (storeMeasurements/storeSegmentation),
// so the display set should already exist at this point.
const displaySet = displaySetService.getMostRecentDisplaySet();
const displaySetInstanceUID = displaySet.displaySetInstanceUID;
uiNotificationService.show({
title: 'Create Report',
message: `${reportType} saved successfully`,
message: successMessage ?? `${reportType} saved successfully`,
type: 'success',
});
@@ -4,6 +4,7 @@ import qs from 'query-string';
import getImageId from '../DicomWebDataSource/utils/getImageId';
import getDirectURL from '../utils/getDirectURL';
import { resolveConfigFetchPolicy, fetchConfigJson } from '../utils/secureConfigFetch';
const metadataProvider = OHIF.classes.MetadataProvider;
@@ -59,13 +60,18 @@ const findStudies = (key, value) => {
return studies;
};
function createDicomJSONApi(dicomJsonConfig) {
function createDicomJSONApi(dicomJsonConfig, servicesManager) {
const { userAuthenticationService } = servicesManager.services;
const implementation = {
initialize: async ({ query, url }) => {
if (!url) {
url = query.get('url');
}
let metaData = getMetaDataByURL(url);
const evaluatedUrl = resolveConfigFetchPolicy(url, {
allowedOrigins: dicomJsonConfig.dangerouslyAllowedOriginsForAuthenticatedEnvironments,
userAuthenticationService,
});
let metaData = getMetaDataByURL(evaluatedUrl.normalizedUrl);
// if we have already cached the data from this specific url
// We are only handling one StudyInstanceUID to run; however,
@@ -76,8 +82,7 @@ function createDicomJSONApi(dicomJsonConfig) {
});
}
const response = await fetch(url);
const data = await response.json();
const data = await fetchConfigJson(evaluatedUrl);
let StudyInstanceUID;
let SeriesInstanceUID;
@@ -105,11 +110,11 @@ function createDicomJSONApi(dicomJsonConfig) {
});
_store.urls.push({
url,
url: evaluatedUrl.normalizedUrl,
studies: [...data.studies],
});
_store.studyInstanceUIDMap.set(
url,
evaluatedUrl.normalizedUrl,
data.studies.map(study => study.StudyInstanceUID)
);
},
@@ -252,6 +257,8 @@ function createDicomJSONApi(dicomJsonConfig) {
console.warn(' DICOMJson store dicom not implemented');
},
},
reject: {},
deleteStudyMetadataPromise: () => {},
getImageIdsForDisplaySet(displaySet) {
const images = displaySet.images;
const imageIds = [];
@@ -297,8 +304,21 @@ function createDicomJSONApi(dicomJsonConfig) {
},
getStudyInstanceUIDs: ({ params, query }) => {
const url = query.get('url');
return _store.studyInstanceUIDMap.get(url);
if (!url) {
return;
}
try {
const evaluatedUrl = resolveConfigFetchPolicy(url, {
allowedOrigins: dicomJsonConfig.dangerouslyAllowedOriginsForAuthenticatedEnvironments,
userAuthenticationService,
});
return _store.studyInstanceUIDMap.get(evaluatedUrl.normalizedUrl);
} catch {
return;
}
},
getConfig: () => dicomJsonConfig,
};
return IWebApiDataSource.create(implementation);
}
@@ -1,5 +1,6 @@
import { IWebApiDataSource } from '@ohif/core';
import { createDicomWebApi } from '../DicomWebDataSource/index';
import { resolveConfigFetchPolicy, fetchConfigJson } from '../utils/secureConfigFetch';
/**
* This datasource is initialized with a url that returns a JSON object with a
@@ -11,6 +12,7 @@ import { createDicomWebApi } from '../DicomWebDataSource/index';
*/
function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager: AppTypes.ServicesManager) {
const { name } = dicomWebProxyConfig;
const { userAuthenticationService } = servicesManager.services;
let dicomWebDelegate = undefined;
const implementation = {
@@ -20,12 +22,14 @@ function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager: AppTypes.S
if (!url) {
throw new Error(`No url for '${name}'`);
} else {
const response = await fetch(url);
const data = await response.json();
const evaluatedUrl = resolveConfigFetchPolicy(url, {
allowedOrigins: dicomWebProxyConfig.dangerouslyAllowedOriginsForAuthenticatedEnvironments,
userAuthenticationService,
});
const data = await fetchConfigJson(evaluatedUrl);
if (!data.servers?.dicomWeb?.[0]) {
throw new Error('Invalid configuration returned by url');
}
dicomWebDelegate = createDicomWebApi(
data.servers.dicomWeb[0].configuration || data.servers.dicomWeb[0],
servicesManager
@@ -54,9 +58,16 @@ function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager: AppTypes.S
store: {
dicom: (...args) => dicomWebDelegate.store.dicom(...args),
},
deleteStudyMetadataPromise: (...args) => dicomWebDelegate.deleteStudyMetadataPromise(...args),
getImageIdsForDisplaySet: (...args) => dicomWebDelegate.getImageIdsForDisplaySet(...args),
getImageIdsForInstance: (...args) => dicomWebDelegate.getImageIdsForInstance(...args),
reject: {
series: (...args) => dicomWebDelegate?.reject?.series?.(...args),
},
deleteStudyMetadataPromise: (...args) => dicomWebDelegate?.deleteStudyMetadataPromise?.(...args),
getImageIdsForDisplaySet: (...args) => dicomWebDelegate?.getImageIdsForDisplaySet?.(...args),
getImageIdsForInstance: (...args) => dicomWebDelegate?.getImageIdsForInstance?.(...args),
getConfig: (...args) =>
dicomWebDelegate?.getConfig?.(...args) ?? {
dicomUploadEnabled: false,
},
getStudyInstanceUIDs({ params, query }) {
let studyInstanceUIDs = [];
@@ -87,7 +87,7 @@ function PanelStudyBrowser({
commandsManager,
servicesManager,
isHangingProtocolLayout,
appConfig: extensionManager._appConfig,
appConfig: extensionManager.appConfig,
};
const handlers = customHandler?.callbacks.map(callback => callback(setupArgs));
@@ -30,6 +30,7 @@ export default function CreateReportDialogPrompt({
predecessorImageId,
extensionManager,
servicesManager,
enableDownload = false,
}): Promise<{
value: string;
dataSourceName: string;
@@ -59,6 +60,7 @@ export default function CreateReportDialogPrompt({
predecessorImageId,
minSeriesNumber,
modality,
enableDownload,
onSave: async ({
reportName,
dataSource: selectedDataSource,
@@ -101,6 +101,7 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
<Button
variant="ghost"
className="hover:bg-muted"
data-cy="undo-btn"
onClick={() => {
commandsManager.run('undo');
}}
@@ -110,6 +111,7 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
<Button
variant="ghost"
className="hover:bg-muted"
data-cy="redo-btn"
onClick={() => {
commandsManager.run('redo');
}}
+89 -16
View File
@@ -1,4 +1,7 @@
import { Types, DicomMetadataStore } from '@ohif/core';
import { Types, DicomMetadataStore, utils } from '@ohif/core';
import dcmjs from 'dcmjs';
const { downloadBlob } = utils;
import { ContextMenuController } from './CustomizableContextMenu';
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
@@ -66,8 +69,8 @@ const commandsModule = ({
*/
addDisplaySetAsLayer: ({ viewportId, displaySetInstanceUID, removeFirst = false }) => {
if (!viewportId) {
const { activeViewportId } = servicesManager.services.viewportGridService.getState();
viewportId = activeViewportId;
const { activeViewportId } = servicesManager.services.viewportGridService.getState();
viewportId = activeViewportId;
}
if (!viewportId || !displaySetInstanceUID) {
@@ -151,6 +154,19 @@ const commandsModule = ({
return;
}
// Check if it's a segmentation and handle accordingly.
// Note that for the sake of hydrated segmentations, we remove the
// segmentation before checking if the display set is indeed in the viewport.
// This is because hydrated segmentations are not in the viewport per se
// {i.e. they are not layered) but are simply referenced by the display
// set in the viewport.
const isSegmentation = DERIVED_OVERLAY_MODALITIES.includes(displaySet.Modality);
if (isSegmentation) {
segmentationService.removeRepresentationsFromViewport(viewportId, {
segmentationId: displaySetInstanceUID,
});
}
// Get current display sets for the viewport
const currentDisplaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
@@ -159,14 +175,6 @@ const commandsModule = ({
return;
}
// Check if it's a segmentation and handle accordingly
const isSegmentation = DERIVED_OVERLAY_MODALITIES.includes(displaySet.Modality);
if (isSegmentation) {
segmentationService.removeSegmentationRepresentations(viewportId, {
segmentationId: displaySetInstanceUID,
});
}
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportId,
displaySetInstanceUID
@@ -651,7 +659,9 @@ const commandsModule = ({
const displaySets = displaySetService.activeDisplaySets;
const { UIModalService } = servicesManager.services;
const defaultDisplaySetInstanceUID = displaySetInstanceUID || displaySetInstanceUIDs[0];
const defaultDisplaySetInstanceUID =
displaySetInstanceUID || displaySetInstanceUIDs[0] || displaySets[0]?.displaySetInstanceUID;
UIModalService.show({
content: DicomTagBrowser,
contentProps: {
@@ -724,10 +734,11 @@ const commandsModule = ({
displaySetIndexToShow > -1 && displaySetIndexToShow < currentDisplaySets.length;
displaySetIndexToShow += direction
) {
if (
!excludeNonImageModalities ||
!nonImageModalities.includes(currentDisplaySets[displaySetIndexToShow].Modality)
) {
const nextDisplaySet = currentDisplaySets[displaySetIndexToShow];
if (nextDisplaySet.madeInClient) {
continue;
}
if (!excludeNonImageModalities || !nonImageModalities.includes(nextDisplaySet.Modality)) {
break;
}
}
@@ -761,6 +772,67 @@ const commandsModule = ({
setTimeout(() => actions.scrollActiveThumbnailIntoView(), 0);
},
/**
* Creates a store function based on the data source type.
* @param dataSource - 'download', 'copyToClipboard', or a named data source
* @param defaultFileName - Default filename for download/clipboard
* @param defaultContentType - Default content type for clipboard
* @returns A store function, or null if no valid store exists
*/
createStoreFunction: ({ dataSource, defaultFileName, defaultContentType }) => {
if (dataSource === 'download') {
return async dicom => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
DicomMetadataStore.addInstances(instances, true);
if (instances.length !== 1) {
throw new Error('Download only supports a single DICOM instance');
}
const reportBlob = dcmjs.data.datasetToBlob(instances[0]);
downloadBlob(reportBlob, { filename: defaultFileName || 'dicom.dcm' });
};
}
if (dataSource === 'copyToClipboard') {
return async dicom => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
DicomMetadataStore.addInstances(instances, true);
if (instances.length !== 1) {
throw new Error('Copy to clipboard only supports a single DICOM instance');
}
const reportBlob = dcmjs.data.datasetToBlob(instances[0]);
const type = defaultContentType || 'application/dicom';
await navigator.clipboard.write([
new ClipboardItem({ [type]: new Blob([reportBlob], { type }) }),
]);
};
}
// DICOM STOW path — resolve the named data source
const dataSources = extensionManager.getDataSources(dataSource);
const resolvedDataSource = dataSources?.[0];
if (!resolvedDataSource?.store?.dicom) {
return null;
}
return async (dicom, { dicomDict } = {}) => {
const instances = Array.isArray(dicom) ? dicom : [dicom];
const config = resolvedDataSource.getConfig?.();
if (config?.wadoRoot) {
instances.forEach(instance => {
instance.wadoRoot = config.wadoRoot;
});
}
DicomMetadataStore.addInstances(instances, true);
for (const instance of instances) {
await resolvedDataSource.store.dicom(instance, null, dicomDict);
}
const studyUIDs = new Set(instances.map(i => i.StudyInstanceUID).filter(Boolean));
for (const uid of studyUIDs) {
resolvedDataSource.deleteStudyMetadataPromise(uid);
}
};
},
};
const definitions = {
@@ -789,6 +861,7 @@ const commandsModule = ({
scrollActiveThumbnailIntoView: actions.scrollActiveThumbnailIntoView,
addDisplaySetAsLayer: actions.addDisplaySetAsLayer,
removeDisplaySetLayer: actions.removeDisplaySetLayer,
createStoreFunction: actions.createStoreFunction,
};
return {
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { InputDialog } from '@ohif/ui-next';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
@@ -21,6 +22,7 @@ type ReportDialogProps = {
priorSeriesNumber: number;
}) => void;
onCancel: () => void;
enableDownload?: boolean;
};
function ReportDialog({
@@ -31,8 +33,11 @@ function ReportDialog({
hide,
onSave,
onCancel,
enableDownload = false,
}: ReportDialogProps) {
const { t } = useTranslation('Buttons');
const { servicesManager } = useSystem();
const actionTakenRef = useRef(false);
const [selectedDataSource, setSelectedDataSource] = useState<string | null>(
dataSources?.[0]?.value ?? null
);
@@ -72,6 +77,7 @@ function ReportDialog({
}, [selectedSeries, seriesOptions]);
const handleSave = useCallback(() => {
actionTakenRef.current = true;
onSave({
reportName,
dataSource: selectedDataSource,
@@ -82,11 +88,33 @@ function ReportDialog({
}, [selectedDataSource, selectedSeries, reportName, hide, onSave]);
const handleCancel = useCallback(() => {
actionTakenRef.current = true;
onCancel();
hide();
}, [onCancel, hide]);
const handleDownload = useCallback(() => {
actionTakenRef.current = true;
onSave({
reportName,
dataSource: 'download',
priorSeriesNumber: Math.max(...seriesOptions.map(it => it.seriesNumber)),
series: selectedSeries,
});
hide();
}, [selectedDataSource, selectedSeries, reportName, hide, onSave]);
// Handles the close dialog button/external close as a cancel
useEffect(() => {
return () => {
if (!actionTakenRef.current) {
onCancel();
}
};
}, [onCancel]);
const showDataSourceSelect = dataSources?.length > 1;
const showDownloadButton = enableDownload;
return (
<div className="text-foreground flex min-w-[400px] max-w-md flex-col">
@@ -181,9 +209,11 @@ function ReportDialog({
<div className="flex justify-end gap-2">
<InputDialog>
<InputDialog.Actions>
<InputDialog.ActionsSecondary onClick={handleCancel}>
Cancel
</InputDialog.ActionsSecondary>
{showDownloadButton && (
<InputDialog.ActionsSecondary onClick={handleDownload}>
{t('Download')}
</InputDialog.ActionsSecondary>
)}
<InputDialog.ActionsPrimary onClick={handleSave}>Save</InputDialog.ActionsPrimary>
</InputDialog.Actions>
</InputDialog>
@@ -1,7 +1,14 @@
import { utils } from '@ohif/core';
const { sortingCriteria } = utils;
const { seriesSortCriteria } = sortingCriteria;
/** Stable series order for e2e (Playwright sets TEST_ENV=true via cross-env). */
const sortingCriteriaFn =
process.env.TEST_ENV === 'true'
? seriesSortCriteria.compareSeriesUID
: seriesSortCriteria.seriesInfoSortingCriteria;
export default {
sortingCriteria: sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria,
sortingCriteria: sortingCriteriaFn,
};
@@ -17,9 +17,60 @@ interface HotkeyDefinitions {
[key: string]: HotkeyDefinition;
}
const MODIFIER_OPTIONS = [
{ value: '16', label: 'Shift' },
{ value: '17', label: 'Ctrl' },
{ value: '18', label: 'Alt' },
{ value: '91', label: 'Meta' },
];
const DEFAULT_TOOL_BINDINGS_STORAGE_KEY = 'user-preferred-tool-bindings';
function getToolModifier(
toolGroupService: any,
toolGroupId: string,
toolName: string,
mouseButton: number
): string | null {
if (!toolGroupService) {
return null;
}
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
if (!bindings?.length) {
return null;
}
const modifierBinding = bindings.find(
binding =>
binding.mouseButton === mouseButton &&
binding.modifierKey != null &&
binding.numTouchPoints == null
);
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
}
function getModifierFromBindings(
bindings: Array<Record<string, unknown>> | undefined,
mouseButton: number
): string | null {
if (!bindings?.length) {
return null;
}
const modifierBinding = bindings.find(
binding =>
binding.mouseButton === mouseButton &&
binding.modifierKey != null &&
binding.numTouchPoints == null
);
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
}
function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
const { hotkeysManager } = useSystem();
const { hotkeysManager, servicesManager } = useSystem();
const { t, i18n: i18nextInstance } = useTranslation('UserPreferencesModal');
const toolGroupService = (servicesManager as any)?.services?.toolGroupService;
const { hotkeyDefinitions = {}, hotkeyDefaults = {} } = hotkeysManager;
@@ -51,9 +102,19 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
const currentLanguage = currentLanguageFn();
const initialCrosshairModifier = useMemo(
() => getToolModifier(toolGroupService, 'mpr', 'Crosshairs', 1),
[toolGroupService]
);
const defaultCrosshairBindings = useMemo(
() => toolGroupService?.getDefaultToolBindings?.('mpr', 'Crosshairs'),
[toolGroupService]
);
const [state, setState] = useState({
hotkeyDefinitions: initialHotkeyDefinitions,
languageValue: currentLanguage.value,
crosshairModifier: initialCrosshairModifier,
});
const onLanguageChangeHandler = (value: string) => {
@@ -78,9 +139,17 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
...state,
languageValue: defaultLanguage.value,
hotkeyDefinitions: resolvedHotkeyDefaults,
crosshairModifier: getModifierFromBindings(defaultCrosshairBindings, 1),
}));
hotkeysManager.restoreDefaultBindings();
if (toolGroupService && defaultCrosshairBindings?.length) {
toolGroupService.setToolBindings('mpr', 'Crosshairs', defaultCrosshairBindings);
toolGroupService.applyToolBindings('mpr', 'Crosshairs', {
replaceExisting: true,
});
}
toolGroupService?.removePersistedToolBindings('mpr', 'Crosshairs');
};
const displayNames = React.useMemo(() => {
@@ -165,6 +234,44 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
/>
))}
</UserPreferencesModal.HotkeysGrid>
{state.crosshairModifier != null && (
<>
<UserPreferencesModal.SubHeading>
{t('ModifierKeys', { defaultValue: 'Modifier Keys' })}
</UserPreferencesModal.SubHeading>
<UserPreferencesModal.HotkeysGrid>
<div className="flex items-center justify-between gap-2">
<span className="text-foreground text-base">
{t('CrosshairsModifier', { defaultValue: 'Crosshairs' })}
</span>
<div className="flex items-center gap-1.5">
<span className="text-muted-foreground text-sm">
{t('PlusLeftClick', { defaultValue: 'Left Click +' })}
</span>
<Select
value={state.crosshairModifier}
onValueChange={val => setState(s => ({ ...s, crosshairModifier: val }))}
>
<SelectTrigger className="w-16">
<SelectValue />
</SelectTrigger>
<SelectContent>
{MODIFIER_OPTIONS.map(opt => (
<SelectItem
key={opt.value}
value={opt.value}
>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</UserPreferencesModal.HotkeysGrid>
</>
)}
</UserPreferencesModal.Body>
<FooterAction>
<FooterAction.Left>
@@ -191,6 +298,18 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
return; // Exit early since we're reloading
}
hotkeysManager.setHotkeys(state.hotkeyDefinitions);
if (toolGroupService && state.crosshairModifier != null) {
const bindings = [
{ mouseButton: 1, modifierKey: Number(state.crosshairModifier) },
];
toolGroupService.setToolBindings('mpr', 'Crosshairs', bindings);
toolGroupService.applyToolBindings('mpr', 'Crosshairs', {
replaceExisting: true,
});
toolGroupService.persistToolBindings('mpr', 'Crosshairs', bindings);
}
hotkeysModule.stopRecord();
hotkeysModule.unpause();
hide();
@@ -206,4 +325,5 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
export default {
'ohif.userPreferencesModal': UserPreferencesModalDefault,
'ohif.userPreferences.toolBindingsStorageKey': DEFAULT_TOOL_BINDINGS_STORAGE_KEY,
};
@@ -6,13 +6,7 @@ import getDisplaySetsFromUnsupportedSeries from './getDisplaySetsFromUnsupported
import { chartHandler } from './SOPClassHandlers/chartSOPClassHandler';
import { metaData } from '@cornerstonejs/core';
const {
isImage,
sortStudyInstances,
instancesSortCriteria,
sopClassDictionary,
isDisplaySetReconstructable,
} = utils;
const { isImage, sortStudyInstances, sopClassDictionary, isDisplaySetReconstructable } = utils;
const { ImageSet } = classes;
const DEFAULT_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
@@ -54,12 +48,20 @@ function getDisplaySetInfo(instances) {
let firstTimePointInstances;
if (instances[0].NumberOfFrames > 1 && timePoints.length > 1) {
// handle multiframe dynamic volume
firstTimePointInstances = timePoints[0].map(imageId => metaData.get('instance', imageId));
// Handle multiframe dynamic volumes. Local file frame imageIds do not
// always resolve to a frame-level instance object, so keep resolved
// entries and fall back to the source multiframe instance when needed.
firstTimePointInstances = timePoints[0]
.map(imageId => metaData.get('instance', imageId))
.filter(Boolean);
if (!firstTimePointInstances.length) {
firstTimePointInstances = [instances[0]];
}
} else {
// O(n) to convert it into a map and O(1) to find each instance
instances.forEach(instance => instancesMap.set(instance.imageId, instance));
firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId));
firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId)).filter(Boolean);
}
displaySetInfo = isDisplaySetReconstructable(firstTimePointInstances, appConfig);
} else {
@@ -267,6 +269,7 @@ const sopClassUids = [
sopClassDictionary.XRay3DAngiographicImageStorage,
sopClassDictionary.XRay3DCraniofacialImageStorage,
sopClassDictionary.BreastTomosynthesisImageStorage,
sopClassDictionary.CornealTopographyMapStorage,
sopClassDictionary.BreastProjectionXRayImageStorageForPresentation,
sopClassDictionary.BreastProjectionXRayImageStorageForProcessing,
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation,
Loaded 100 of 446 files, more files were not shown because too many files have changed in this diff. Show more