Compare commits

...
Author SHA1 Message Date
ohif-bot 43c882ac87 chore(version): Update package versions [skip ci] 2023-10-19 17:39:23 +00:00
ohif-bot 912b3c7c53 chore(version): version.json [skip ci] 2023-10-19 17:39:15 +00:00
d9258eca70 fix(cine): Use the frame rate specified in DICOM and optionally auto play cine (#3735)
Co-authored-by: rareramos <rareramos1126@gmail.com>
Co-authored-by: Doug Horner <horner@mieweb.com>
Co-authored-by: Rehan <58819707+Rayhan-011@users.noreply.github.com>
2023-10-19 13:29:44 -04:00
ohif-bot fa599d05fb chore(version): Update package versions [skip ci] 2023-10-19 17:10:43 +00:00
ohif-bot 5f8325e338 chore(version): version.json [skip ci] 2023-10-19 17:10:36 +00:00
Joe Boccanfuso 93d798db99 fix(calibration): No calibration popup caused by perhaps an unused code optimization for production builds (#3736) 2023-10-19 13:02:03 -04:00
ohif-bot 33f1259408 chore(version): Update package versions [skip ci] 2023-10-12 21:18:38 +00:00
ohif-bot c54658a320 chore(version): version.json [skip ci] 2023-10-12 21:18:31 +00:00
Alireza 55002fbd71 chore(version): move to the next beta version [BUMP BETA] 2023-10-12 17:09:44 -04:00
ohif-bot 3f9ee1451b chore(version): Update package versions [skip ci] 2023-10-11 16:10:32 +00:00
ohif-bot 70ea13ec1f chore(version): version.json [skip ci] 2023-10-11 16:10:25 +00:00
Alireza 157b88c909 fix(display messages): broken after timings (#3719) 2023-10-11 12:01:32 -04:00
ohif-bot 75c3f94177 chore(version): Update package versions [skip ci] 2023-10-11 14:19:25 +00:00
ohif-bot 6874d5f0b9 chore(version): version.json [skip ci] 2023-10-11 14:19:16 +00:00
Alireza a3f2a1a7b0 fix(export): wrong export for the tmtv RT function (#3715) 2023-10-11 10:10:29 -04:00
ohif-bot 8067f0c62c chore(version): Update package versions [skip ci] 2023-10-10 19:30:50 +00:00
ohif-bot a894b6ffc5 chore(version): version.json [skip ci] 2023-10-10 19:30:41 +00:00
Joe Boccanfuso c3a5847dcd fix(i18n): display set(s) are two words for English messages (#3711) 2023-10-10 15:20:47 -04:00
ohif-bot 132938bbe0 chore(version): Update package versions [skip ci] 2023-10-10 17:32:24 +00:00
ohif-bot 5ba63bd010 chore(version): version.json [skip ci] 2023-10-10 17:32:14 +00:00
Alireza 7c57f67844 fix(modules): add stylus loader as an option to be uncommented (#3710) 2023-10-10 13:21:17 -04:00
ohif-bot dc929d3df5 chore(version): Update package versions [skip ci] 2023-10-10 14:36:12 +00:00
ohif-bot 5a3af83168 chore(version): version.json [skip ci] 2023-10-10 14:36:03 +00:00
Joe Boccanfuso a9a6ad50ea fix(segmentation): Various fixes for segmentation mode and other (#3709) 2023-10-10 10:25:30 -04:00
ohif-bot f8455056cd chore(version): Update package versions [skip ci] 2023-10-10 00:58:12 +00:00
ohif-bot f9c3d510d3 chore(version): version.json [skip ci] 2023-10-10 00:58:05 +00:00
Alireza 52f34c64d0 fix(voi): should publish voi change event on reset (#3707) 2023-10-09 20:49:10 -04:00
ohif-bot 7d87766ac4 chore(version): Update package versions [skip ci] 2023-10-09 17:58:57 +00:00
ohif-bot 820bb83b70 chore(version): version.json [skip ci] 2023-10-09 17:58:49 +00:00
Alireza 0a42d573bb fix(modality unit): fix the modality unit per target via upgrade of cs3d (#3706) 2023-10-09 13:48:31 -04:00
ohif-bot f9f33f74c1 chore(version): Update package versions [skip ci] 2023-10-09 15:13:37 +00:00
ohif-bot 6a5fe59136 chore(version): version.json [skip ci] 2023-10-09 15:13:28 +00:00
Alireza 4911e4796c fix(segmentation): do not use SAB if not specified (#3705) 2023-10-09 11:02:49 -04:00
ohif-bot 349c503f04 chore(version): Update package versions [skip ci] 2023-10-06 19:14:47 +00:00
ohif-bot a6a6aed361 chore(version): version.json [skip ci] 2023-10-06 19:14:40 +00:00
dxlinandAlireza 40673f64b3 feat(Segmentation): download RTSS from Labelmap(#3692)
Co-authored-by: Alireza <ar.sedghi@gmail.com>
2023-10-06 15:00:04 -04:00
ohif-bot eab42c1032 chore(version): Update package versions [skip ci] 2023-10-06 03:49:04 +00:00
ohif-bot a790be9f5a chore(version): version.json [skip ci] 2023-10-06 03:48:56 +00:00
Alireza 8bc12a37d0 fix(bugs): fixing lots of bugs regarding release candidate (#3700) 2023-10-05 23:39:56 -04:00
ohif-bot 29aeb070e0 chore(version): Update package versions [skip ci] 2023-10-06 02:22:47 +00:00
ohif-bot cdb66dcdef chore(version): version.json [skip ci] 2023-10-06 02:22:39 +00:00
Joe Boccanfuso 1fd98d9220 fix(segmentation scroll): and hydration bugs (#3701) 2023-10-05 22:13:08 -04:00
ohif-bot 748acaf9c1 chore(version): Update package versions [skip ci] 2023-10-04 18:25:03 +00:00
ohif-bot 3f27c3820f chore(version): version.json [skip ci] 2023-10-04 18:24:55 +00:00
Joe Boccanfuso c1d5ee7e3f fix(measurement and microscopy): various small fixes for measurement and microscopy side panel (#3696) 2023-10-04 14:11:09 -04:00
ohif-bot 646bef1d93 chore(version): Update package versions [skip ci] 2023-10-04 17:22:52 +00:00
ohif-bot 83c001f8e4 chore(version): version.json [skip ci] 2023-10-04 17:22:43 +00:00
Alirezaandpwespi ebe8f71da2 feat(locale): add German translations - community PR (#3697)
Co-authored-by: pwespi <pwespi@pm.me>
2023-10-04 13:11:31 -04:00
ohif-bot 17f528bcde chore(version): Update package versions [skip ci] 2023-10-04 16:34:07 +00:00
ohif-bot ed82511acc chore(version): version.json [skip ci] 2023-10-04 16:33:58 +00:00
AlirezaandAhmet Altay 745050a28e feat(locale): Added Turkish language support (tr-TR) - Community PR (#3695)
Co-authored-by: Ahmet Altay <46381367+ahmetaltay33@users.noreply.github.com>
2023-10-04 12:22:26 -04:00
ohif-bot 953c564d98 chore(version): Update package versions [skip ci] 2023-10-04 16:17:52 +00:00
ohif-bot c418185f0b chore(version): version.json [skip ci] 2023-10-04 16:17:44 +00:00
wangxuan 29748d46a1 fix(translation): Side panel translate fix (#3156) 2023-10-04 12:07:30 -04:00
ohif-bot a755cce7f1 chore(version): Update package versions [skip ci] 2023-10-04 15:37:52 +00:00
ohif-bot 42b7d9983c chore(version): version.json [skip ci] 2023-10-04 15:37:44 +00:00
Joe Boccanfuso 28cec04ff4 fix(cli): Add npm packaged mode not working (#3689) 2023-10-04 11:27:55 -04:00
ohif-bot 01d843f267 chore(version): Update package versions [skip ci] 2023-10-03 21:11:31 +00:00
ohif-bot 15eca78b68 chore(version): version.json [skip ci] 2023-10-03 21:11:23 +00:00
Bill Wallace 108383b9ef feat(debug): Add timing information about time to first image/all images, and query time (#3681) 2023-10-03 17:01:00 -04:00
ohif-bot 1b207f14d2 chore(version): Update package versions [skip ci] 2023-10-03 19:28:23 +00:00
ohif-bot 38cd3d8afc chore(version): version.json [skip ci] 2023-10-03 19:28:14 +00:00
AlirezaandOuwen Huang 5e7fe91617 feat(displayArea): add display area to hanging protocol (#3691)
Co-authored-by: Ouwen Huang <ouwen.oh@gmail.com>
2023-10-03 15:18:15 -04:00
ohif-bot 64f68f58c2 chore(version): Update package versions [skip ci] 2023-10-03 18:36:48 +00:00
ohif-bot 49bc346661 chore(version): version.json [skip ci] 2023-10-03 18:36:39 +00:00
Yaroslav Halchenko 5cc1dc9b9c docs(links): Adding references to two used markdown links (#3650) 2023-10-03 14:26:31 -04:00
ohif-bot 2a086a699a chore(version): Update package versions [skip ci] 2023-10-03 18:12:49 +00:00
ohif-bot 58f214bca5 chore(version): version.json [skip ci] 2023-10-03 18:12:41 +00:00
rodrigobasilio2022 4dc2acdefa fix(editing): regression bug in disable editing (#3687) 2023-10-03 14:02:21 -04:00
ohif-bot 4c88a4a2fd chore(version): Update package versions [skip ci] 2023-10-03 16:35:17 +00:00
ohif-bot 06facd8ea2 chore(version): version.json [skip ci] 2023-10-03 16:35:08 +00:00
Edward Sonandedward65 eb22328fc0 fix(typescript error): Change pubSubServiceInterface file type to typescript (#3546)
Co-authored-by: edward65 <edward@afxmedical.com>
2023-10-03 12:23:40 -04:00
ohif-bot b33ad0c7e9 chore(version): Update package versions [skip ci] 2023-10-03 15:55:03 +00:00
ohif-bot 3415cf909f chore(version): version.json [skip ci] 2023-10-03 15:54:54 +00:00
Joe BoccanfusoandAlireza e36a604331 fix(dicom overlay): Handle special cases of ArrayBuffer for various DICOM overlay attributes. (#3684)
Co-authored-by: Alireza <ar.sedghi@gmail.com>
2023-10-03 11:44:56 -04:00
Bill Wallace 8a335bd03d fix(StackSync): Miscellaneous fixes for stack image sync (#3663) 2023-10-03 10:59:09 -04:00
ohif-bot b3429729f1 chore(version): Update package versions [skip ci] 2023-10-03 14:22:22 +00:00
ohif-bot e2e806f188 chore(version): version.json [skip ci] 2023-10-03 14:22:12 +00:00
Alireza 1129c155d2 fix(config): support more values for the useSharedArrayBuffer (#3688) 2023-10-03 10:11:32 -04:00
ohif-bot c9c57b04d3 chore(version): Update package versions [skip ci] 2023-09-29 22:04:50 +00:00
ohif-bot d09938b72a chore(version): version.json [skip ci] 2023-09-29 22:04:41 +00:00
Alireza a67d72de85 fix(no sab): should work when shared array buffer is not required (#3686) 2023-09-29 17:54:16 -04:00
ohif-bot 3dabee2342 chore(version): Update package versions [skip ci] 2023-09-29 14:51:58 +00:00
ohif-bot 66abdace30 chore(version): version.json [skip ci] 2023-09-29 14:51:49 +00:00
Joe BoccanfusoandAlireza dc73b18748 fix(cli): various fixes for adding custom modes and extensions (#3683)
Co-authored-by: Alireza <ar.sedghi@gmail.com>
2023-09-29 10:35:30 -04:00
ohif-bot 52da92fed4 chore(version): Update package versions [skip ci] 2023-09-26 20:58:57 +00:00
ohif-bot cfbb3cfded chore(version): version.json [skip ci] 2023-09-26 20:58:49 +00:00
Joe Boccanfuso 86f54d0d07 fix(toggleOneUp): fixed one up for main tmtv layout (#3677) 2023-09-26 16:49:06 -04:00
ohif-bot 485cf037a1 chore(version): Update package versions [skip ci] 2023-09-26 19:38:00 +00:00
ohif-bot 3d38122b84 chore(version): version.json [skip ci] 2023-09-26 19:37:51 +00:00
Alireza 8feb5ee3f9 chore(publish): fix timeout problem without lerna (#3679) 2023-09-26 15:27:24 -04:00
ohif-bot 1f906963e7 chore(version): Update package versions [skip ci] 2023-09-26 15:54:37 +00:00
ohif-bot 9a4aa1a5aa chore(version): version.json [skip ci] 2023-09-26 15:54:28 +00:00
Alireza fcd323fa8c chore(publish): fix timeout problem (#3678) 2023-09-26 11:43:37 -04:00
ohif-bot b6e33224c8 chore(version): Update package versions [skip ci] 2023-09-26 13:03:46 +00:00
ohif-bot b8e519a4a9 chore(version): version.json [skip ci] 2023-09-26 13:03:37 +00:00
Alireza 0bf9949f45 chore(build): try to publish to npm again (#3676) 2023-09-26 08:53:40 -04:00
ohif-bot 3431b103e2 chore(version): Update package versions [skip ci] 2023-09-26 03:23:59 +00:00
ohif-bot 00918d86d3 chore(version): version.json [skip ci] 2023-09-26 03:23:52 +00:00
Alireza c86b129f58 chore(build): try to publish to npm (#3675) 2023-09-25 23:13:43 -04:00
ohif-bot 10ca35d5f4 chore(version): Update package versions [skip ci] 2023-09-22 20:00:44 +00:00
ohif-bot a308f7b4b6 chore(version): version.json [skip ci] 2023-09-22 20:00:36 +00:00
Sofien-Sellami 04ca10d877 fix(react-select): update react select package (#3622) 2023-09-22 15:48:12 -04:00
Joe Boccanfuso 85c899b399 feat(SidePanel): new side panel tab look-and-feel (#3657) 2023-09-22 15:36:44 -04:00
Alireza 48bbd6281a feat(segmentation mode): Add create, and export SEG with Brushes (#3632) 2023-09-22 10:23:44 -04:00
ohif-bot 896866e9d0 chore(version): Update package versions [skip ci] 2023-09-22 13:40:25 +00:00
ohif-bot a02a5ded9a chore(version): version.json [skip ci] 2023-09-22 13:40:16 +00:00
AlirezaandOuwen Huang 2bd3b26a6a perf(memory): add 16 bit texture via configuration - reduces memory by half (#3662)
Co-authored-by: Ouwen Huang <ouwen.huang@duke.edu>
2023-09-22 09:31:45 -04:00
ohif-bot 034a6d7696 chore(version): Update package versions [skip ci] 2023-09-21 20:53:19 +00:00
ohif-bot 2fc28978da chore(version): version.json [skip ci] 2023-09-21 20:53:11 +00:00
Joe Boccanfuso 221dedde5d fix(mpr): Return the original/raw hanging protocol when fetching and preserving the current active protocol. (#3670) 2023-09-21 16:45:15 -04:00
ohif-bot 807bd663b3 chore(version): Update package versions [skip ci] 2023-09-21 11:38:01 +00:00
ohif-bot 0318504597 chore(version): version.json [skip ci] 2023-09-21 11:37:54 +00:00
Alireza d609ae1edb docs(url): fix incorrect url param in the doc (#3667) 2023-09-21 07:30:01 -04:00
ohif-bot 2132f0777e chore(version): Update package versions [skip ci] 2023-09-19 14:19:42 +00:00
ohif-bot 10ecd4997b chore(version): version.json [skip ci] 2023-09-19 14:19:34 +00:00
AlirezaandJoe Boccanfuso 2d7721cb58 fix(keyCloak): fix openresty keycloak deployment recipe (#3655)
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2023-09-19 10:11:32 -04:00
401 changed files with 18319 additions and 5199 deletions

No files matched your search

+209 -164
View File
@@ -13,22 +13,24 @@ version: 2.1
##
orbs:
codecov: codecov/codecov@1.0.5
cypress: cypress-io/cypress@1.26.0
cypress: cypress-io/cypress@3.1.4
executors:
# Custom executor to override Cypress config
deploy-to-prod-executor:
cypress-custom:
description: |
Single Docker container used to run Cypress Tests
docker:
- image: cimg/node:16.14
environment:
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
chrome-and-pacs:
docker:
# Primary container image where all steps run.
- image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102'
- image: cimg/node:<< parameters.node-version >>-browsers
parameters:
node-version:
default: '18.16.1'
description: |
The version of Node to run your tests with.
type: string
defaults: &defaults
docker:
- image: cimg/node:16.14-browsers
- image: cimg/node:18.18
environment:
TERM: xterm # Enable colors in term
QUICK_BUILD: true
@@ -43,6 +45,7 @@ jobs:
steps:
# Update yarn
- run: yarn -v
- run: node --version
# Checkout code and ALL Git Tags
- checkout
- restore_cache:
@@ -90,43 +93,6 @@ jobs:
file: '/home/circleci/repo/platform/core/coverage/reports'
flags: 'core'
###
# Workflow: PR_OPTIONAL_DOCKER_PUBLISH
###
DOCKER_PR_PUBLISH:
<<: *defaults
steps:
# Enable yarn workspaces
- run: yarn config set workspaces-experimental true
# Checkout code and ALL Git Tags
- checkout
- restore_cache:
name: Restore Yarn and Cypress Package Cache
keys:
# when lock file changes, use increasingly general patterns to restore cache
- yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages-
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image
command: |
# Remove npm config
rm -f ./.npmrc
# Set our version number using vars
echo $CIRCLE_BUILD_NUM
# Build our image, auth, and push
docker build --tag ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM
###
# Workflow: DEPLOY
###
@@ -177,62 +143,52 @@ jobs:
- commit.txt
- version.json
# DEPLOY_TO_DEV:
# docker:
# - image: circleci/node:16.14.0
# environment:
# TERM: xterm
# NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
# working_directory: ~/repo
# steps:
# - attach_workspace:
# at: ~/repo
# - run: cd .netlify && npm install
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
# - run: cd .netlify && npm run deploy
# DEPLOY_TO_STAGING:
# docker:
# - image: circleci/node:16.14.0
# environment:
# TERM: xterm
# NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
# working_directory: ~/repo
# steps:
# - attach_workspace:
# at: ~/repo
# - run: cd .netlify && npm install
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
# - run: cd .netlify && npm run deploy
# DEPLOY_TO_PRODUCTION:
# docker:
# - image: circleci/node:16.14.0
# environment:
# TERM: xterm
# NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0
# working_directory: ~/repo
# steps:
# - attach_workspace:
# at: ~/repo
# - run: cd .netlify && npm install
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
# - run: cd .netlify && npm run deploy
# DEPLOY_TO_RELEASE_DEV:
# docker:
# - image: circleci/node:16.14.0
# environment:
# TERM: xterm
# NETLIFY_SITE_ID: 3270878-22
# working_directory: ~/repo
# steps:
# - attach_workspace:
# at: ~/repo
# - run: cd .netlify && npm install
# - run:
# cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
# - run: cd .netlify && npm run deploy
# just to make sure later on we can publish them
BUILD_PACKAGES_QUICK:
<<: *defaults
steps:
- run: yarn -v
# Checkout code and ALL Git Tags
- checkout
- attach_workspace:
at: ~/repo
# Use increasingly general patterns to restore cache
- restore_cache:
name: Restore Yarn and Cypress Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- yarn-packages-
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile
- save_cache:
name: Save Yarn Package Cache
paths:
- ~/.cache/yarn
key: yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Avoid hosts unknown for github
command: |
rm -rf ~/.ssh
mkdir ~/.ssh/
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
git config --global user.name "ohif-bot"
- run:
name: Authenticate with NPM registry
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
- run:
name: Increase the event emitter limit
command: |
node ./increaseEventEmitterLimit.mjs
- run:
name: build half of the packages (to avoid out of memory in circleci)
command: |
yarn run build:package-all
- run:
name: build the other half of the packages
command: |
yarn run build:package-all-1
###
# Workflow: RELEASE
@@ -282,6 +238,14 @@ jobs:
name: build the other half of the packages
command: |
yarn run build:package-all-1
- run:
name: increase min time out
command: |
npm config set fetch-retry-mintimeout 20000
- run:
name: increase max time out
command: |
npm config set fetch-retry-maxtimeout 120000
- run:
name: publish package versions
command: |
@@ -362,71 +326,152 @@ jobs:
docker push ohif/app:$IMAGE_VERSION_FULL
fi
workflows:
version: 2
# This is copied from the Cypress orb since the default for cypress/run is node 16 and
# we migrated to 18
CYPRESS_CUSTOM_RUN:
description: |
A single, complete job to run Cypress end-to-end tests in your application.
executor: cypress-custom
parallelism: << parameters.parallelism >>
parameters:
cypress-cache-key:
default: cypress-cache-{{ arch }}-{{ checksum "package.json" }}
description: Cache key used to cache the Cypress binary.
type: string
cypress-cache-path:
default: ~/.cache/Cypress
description: |
By default, this will cache the '~/.cache/Cypress' directory so that the Cypress binary is cached. You can override this by providing your own cache path.
type: string
cypress-command:
default: npx cypress run
description: Command used to run your Cypress tests
type: string
include-branch-in-node-cache-key:
default: false
description: |
If true, this cache will only apply to runs within the same branch. (Adds -{{ .Branch }}- to the node cache key)
type: boolean
install-browsers:
default: false
description: |
Cypress runs by default in the Electron browser. Use this flag to install additional browsers to run your tests in.
This is only needed if you are passing the `--browser` flag in your `cypress-command`.
This parameter leverages the `circleci/browser-tools` orb and includes Chrome and FireFox.
If you need additional browser support you can set this to false and use an executor with a docker image
that includes the browsers of your choosing. See https://hub.docker.com/r/cypress/browsers/tags
type: boolean
install-command:
default: ''
description: Overrides the default NPM command (npm ci)
type: string
node-cache-version:
default: v1
description:
Change the default node cache version if you need to clear the cache for any reason.
type: string
package-manager:
default: npm
description: Select the default node package manager to use. NPM v5+ Required.
enum:
- npm
- yarn
- yarn-berry
type: enum
parallelism:
default: 1
description: |
Number of Circle machines to use for load balancing, min 1
(requires `parallel` and `record` flags in your `cypress-command`)
type: integer
post-install:
default: ''
description: |
Additional commands to run after running install but before verifying Cypress and saving cache.
type: string
start-command:
default: ''
description: Command used to start your local dev server for Cypress to tests against
type: string
working-directory:
default: ''
description: Directory containing package.json
type: string
steps:
- cypress/install:
cypress-cache-key: << parameters.cypress-cache-key >>
cypress-cache-path: << parameters.cypress-cache-path >>
include-branch-in-node-cache-key: << parameters.include-branch-in-node-cache-key >>
install-browsers: << parameters.install-browsers >>
install-command: << parameters.install-command >>
node-cache-version: << parameters.node-cache-version >>
package-manager: << parameters.package-manager >>
post-install: << parameters.post-install >>
working-directory: << parameters.working-directory >>
- cypress/run-tests:
cypress-command: << parameters.cypress-command >>
start-command: << parameters.start-command >>
working-directory: << parameters.working-directory >>
workflows:
PR_CHECKS:
jobs:
- UNIT_TESTS
# E2E: PWA
- cypress/run:
name: 'E2E: PWA'
executor: chrome-and-pacs
browser: chrome
pre-steps:
- run: |
# Clear yarn cache; use yarn from image (update image to update yarn)
rm -rf ~/.yarn
yarn -v
yarn: true
record: true
store_artifacts: true
working_directory: platform/app
build: yarn test:data
start: yarn run test:e2e:serve
spec: 'cypress/integration/**/*'
wait-on: 'http://localhost:3000'
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/app/cypress/screenshots
- store_artifacts:
path: platform/app/cypress/videos
- store_test_results:
path: platform/app/cypress/results
- BUILD_PACKAGES_QUICK:
filters:
branches:
ignore: master
- UNIT_TESTS:
requires:
- UNIT_TESTS
PR_OPTIONAL_VISUAL_TESTS:
jobs:
- AWAIT_APPROVAL:
type: approval
# Update hub.docker.org
- cypress/run:
name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
yarn: true
store_artifacts: false
working_directory: platform/app
build:
yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
yarn run build
# start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/app/cypress/screenshots
- store_artifacts:
path: platform/app/cypress/videos
- BUILD_PACKAGES_QUICK
- CYPRESS_CUSTOM_RUN:
name: 'Cypress Tests'
context: cypress
matrix:
parameters:
start-command:
- yarn run test:data && yarn run test:e2e:serve
install-browsers:
- true
cypress-command:
- 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run
--record --browser chrome --parallel'
package-manager:
- 'yarn'
cypress-cache-key:
- 'yarn-packages-{{ checksum "yarn.lock" }}'
cypress-cache-path:
- '~/.cache/Cypress'
requires:
- AWAIT_APPROVAL
- BUILD_PACKAGES_QUICK
# PR_OPTIONAL_VISUAL_TESTS:
# jobs:
# - AWAIT_APPROVAL:
# type: approval
# # Update hub.docker.org
# - cypress/run:
# name: 'Generate Percy Snapshots'
# executor: cypress/browsers-chrome76
# browser: chrome
# pre-steps:
# - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
# yarn: true
# store_artifacts: false
# working_directory: platform/app
# build:
# yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
# yarn run build
# # start server --> verify running --> percy + chrome + cypress
# command: yarn run test:e2e:dist
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
# no-workspace: true # Don't persist workspace
# post-steps:
# - store_artifacts:
# path: platform/app/cypress/screenshots
# - store_artifacts:
# path: platform/app/cypress/videos
# requires:
# - AWAIT_APPROVAL
# Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is
# deployed from the release branch which is more stable and less frequently updated.
-48
View File
@@ -1,48 +0,0 @@
worker_processes 1;
events { worker_connections 1024; }
http {
upstream orthanc-server {
server orthanc:8042;
}
server {
listen [::]:80 default_server;
listen 80;
# CORS Magic
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow_Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow_Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://orthanc:8042;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
# CORS Magic
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow_Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
}
}
}
-89
View File
@@ -1,89 +0,0 @@
{
"Name": "Orthanc inside Docker",
"StorageDirectory": "/var/lib/orthanc/db",
"IndexDirectory": "/var/lib/orthanc/db",
"StorageCompression": false,
"MaximumStorageSize": 0,
"MaximumPatientCount": 0,
"LuaScripts": [],
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
"ConcurrentJobs": 2,
"HttpServerEnabled": true,
"HttpPort": 8042,
"HttpDescribeErrors": true,
"HttpCompressionEnabled": true,
"DicomServerEnabled": true,
"DicomAet": "ORTHANC",
"DicomCheckCalledAet": false,
"DicomPort": 4242,
"DefaultEncoding": "Latin1",
"DeflatedTransferSyntaxAccepted": true,
"JpegTransferSyntaxAccepted": true,
"Jpeg2000TransferSyntaxAccepted": true,
"JpegLosslessTransferSyntaxAccepted": true,
"JpipTransferSyntaxAccepted": true,
"Mpeg2TransferSyntaxAccepted": true,
"RleTransferSyntaxAccepted": true,
"UnknownSopClassAccepted": false,
"DicomScpTimeout": 30,
"RemoteAccessAllowed": true,
"SslEnabled": false,
"SslCertificate": "certificate.pem",
"AuthenticationEnabled": false,
"RegisteredUsers": {
"test": "test"
},
"DicomModalities": {},
"DicomModalitiesInDatabase": false,
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true,
"DicomCheckModalityHost": false,
"DicomScuTimeout": 10,
"OrthancPeers": {},
"OrthancPeersInDatabase": false,
"HttpProxy": "",
"HttpVerbose": true,
"HttpTimeout": 10,
"HttpsVerifyPeers": true,
"HttpsCACertificates": "",
"UserMetadata": {},
"UserContentType": {},
"StableAge": 60,
"StrictAetComparison": false,
"StoreMD5ForAttachments": true,
"LimitFindResults": 0,
"LimitFindInstances": 0,
"LimitJobs": 10,
"LogExportedResources": false,
"KeepAlive": true,
"TcpNoDelay": true,
"HttpThreadsCount": 50,
"StoreDicom": true,
"DicomAssociationCloseDelay": 5,
"QueryRetrieveSize": 10,
"CaseSensitivePN": false,
"LoadPrivateDictionary": true,
"Dictionary": {},
"SynchronousCMove": true,
"JobsHistorySize": 10,
"SaveJobs": true,
"OverwriteInstances": false,
"MediaArchiveSize": 1,
"StorageAccessOnFind": "Always",
"MetricsEnabled": true,
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/",
"EnableWado": true,
"WadoRoot": "/wado",
"Host": "127.0.0.1",
"Ssl": false,
"StowMaxInstances": 10,
"StowMaxSize": 10,
"QidoCaseSensitive": false
}
}
-23
View File
@@ -1,23 +0,0 @@
version: '3.5'
services:
orthanc:
image: jodogne/orthanc-plugins:1.11.0
hostname: orthanc
volumes:
# Config
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
# Persist data
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
ports:
- '4242:4242' # DICOM
- '8042:8042' # Web
restart: unless-stopped
nginx:
image: nginx:latest
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf
ports:
- '80:80' #ngnix proxy
depends_on:
- orthanc
@@ -1,2 +0,0 @@
*
!.gitignore
+1 -1
View File
@@ -64,7 +64,7 @@ body:
attributes:
label: 'Node version'
description: 'Your Node.js version.'
placeholder: 'e.g., 16.14.0'
placeholder: 'e.g., 18.16.1'
validations:
required: true
- type: input
+1 -1
View File
@@ -83,7 +83,7 @@ after the commits are squashed.
#### Tested Environment
- [] OS: <!--[e.g. Windows 10, macOS 10.15.4]-->
- [] Node version: <!--[e.g. 16.14.0]-->
- [] Node version: <!--[e.g. 18.16.1]-->
- [] Browser:
<!--[e.g. Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1]-->
+2
View File
@@ -43,3 +43,5 @@ platform/app/src/pluginImports.js
platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/*
platform/app/.recipes/OpenResty-Orthanc/logs/*
.vercel
.vs
+1 -1
View File
@@ -1 +1 @@
16.14.0
18.16.1
+10
View File
@@ -0,0 +1,10 @@
const stylusToJavaScript = {
test: /\.styl$/,
use: [
{ loader: 'style-loader' }, // 3. Style nodes from JS Strings
{ loader: 'css-loader' }, // 2. CSS to CommonJS
{ loader: 'stylus-loader' }, // 1. Stylus to CSS
],
};
module.exports = stylusToJavaScript;
+26 -15
View File
@@ -18,6 +18,7 @@ const loadShadersRule = require('./rules/loadShaders.js');
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
const cssToJavaScript = require('./rules/cssToJavaScript.js');
const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
// ~~ ENV VARS
const NODE_ENV = process.env.NODE_ENV;
@@ -32,6 +33,27 @@ const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8
//
dotenv.config();
const defineValues = {
/* Application */
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER),
'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
};
// Only redefine updated values. This avoids warning messages in the logs
if (!process.env.APP_CONFIG) {
defineValues['process.env.APP_CONFIG'] = '';
}
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
if (!process.env.NODE_ENV) {
throw new Error('process.env.NODE_ENV not set');
@@ -91,6 +113,9 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
},
},
cssToJavaScript,
// Note: Only uncomment the following if you are using the old style of stylus in v2
// Also you need to uncomment this platform/app/.webpack/rules/extractStyleChunks.js
// stylusToJavaScript,
{
test: /\.wasm/,
type: 'asset/resource',
@@ -133,21 +158,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
},
},
plugins: [
new webpack.DefinePlugin({
/* Application */
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER),
'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
}),
new webpack.DefinePlugin(defineValues),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
+404
View File
@@ -3,6 +3,410 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
### Bug Fixes
* **cine:** Use the frame rate specified in DICOM and optionally auto play cine ([#3735](https://github.com/OHIF/Viewers/issues/3735)) ([d9258ec](https://github.com/OHIF/Viewers/commit/d9258eca70587cf4dc18be4e56c79b16bae73d6d))
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
### Bug Fixes
* **calibration:** No calibration popup caused by perhaps an unused code optimization for production builds ([#3736](https://github.com/OHIF/Viewers/issues/3736)) ([93d798d](https://github.com/OHIF/Viewers/commit/93d798db99c0dee53ef73c376f8a74ac3049cf3f))
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
### Bug Fixes
* **display messages:** broken after timings ([#3719](https://github.com/OHIF/Viewers/issues/3719)) ([157b88c](https://github.com/OHIF/Viewers/commit/157b88c909d3289cb89ace731c1f9a19d40797ac))
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
### Bug Fixes
* **export:** wrong export for the tmtv RT function ([#3715](https://github.com/OHIF/Viewers/issues/3715)) ([a3f2a1a](https://github.com/OHIF/Viewers/commit/a3f2a1a7b0d16bfcc0ecddc2ab731e54c5e377c8))
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
### Bug Fixes
* **i18n:** display set(s) are two words for English messages ([#3711](https://github.com/OHIF/Viewers/issues/3711)) ([c3a5847](https://github.com/OHIF/Viewers/commit/c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d))
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
### Bug Fixes
* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8))
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
### Bug Fixes
* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe))
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
### Bug Fixes
* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332))
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
### Bug Fixes
* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580))
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
### Bug Fixes
* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339))
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
### Features
* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65))
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
### Bug Fixes
* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07))
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
### Bug Fixes
* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8))
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
### Bug Fixes
* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155))
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
### Features
* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6))
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
### Features
* **locale:** Added Turkish language support (tr-TR) - Community PR ([#3695](https://github.com/OHIF/Viewers/issues/3695)) ([745050a](https://github.com/OHIF/Viewers/commit/745050a28ec7c2ef2e9a4d4e590040050b2177b2))
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
### Bug Fixes
* **translation:** Side panel translate fix ([#3156](https://github.com/OHIF/Viewers/issues/3156)) ([29748d4](https://github.com/OHIF/Viewers/commit/29748d46a14d23817dbe196e0f64363fc61a8aed))
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
### Bug Fixes
* **cli:** Add npm packaged mode not working ([#3689](https://github.com/OHIF/Viewers/issues/3689)) ([28cec04](https://github.com/OHIF/Viewers/commit/28cec04ff43b81e218c3e9addef4665b3833a6fe))
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
### Features
* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799))
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
### Features
* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89))
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
### Bug Fixes
* **editing:** regression bug in disable editing ([#3687](https://github.com/OHIF/Viewers/issues/3687)) ([4dc2acd](https://github.com/OHIF/Viewers/commit/4dc2acdefa872dd1d8df47f465e9e9656f95f67f))
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
### Bug Fixes
* **typescript error:** Change pubSubServiceInterface file type to typescript ([#3546](https://github.com/OHIF/Viewers/issues/3546)) ([eb22328](https://github.com/OHIF/Viewers/commit/eb22328fc05d06fc4411805e7a30f826659d796a))
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
### Bug Fixes
* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96))
* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9))
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
### Bug Fixes
* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e))
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
### Bug Fixes
* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5))
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
### Bug Fixes
* **cli:** various fixes for adding custom modes and extensions ([#3683](https://github.com/OHIF/Viewers/issues/3683)) ([dc73b18](https://github.com/OHIF/Viewers/commit/dc73b187484da029a2664bb1302f30137c973b8c))
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
### Bug Fixes
* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5))
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Bug Fixes
* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a))
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
### Performance Improvements
* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab))
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
### Bug Fixes
* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1))
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package ohif-monorepo-root
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
### Bug Fixes
* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74))
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
+2 -2
View File
@@ -21,7 +21,7 @@
# Stage 1: Build the application
# docker build -t ohif/viewer:latest .
FROM node:16.15.0-slim as json-copier
FROM node:18.16.1-slim as json-copier
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
@@ -37,7 +37,7 @@ COPY platform /usr/src/app/platform
#RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
# Copy Files
FROM node:16.15.0-slim as builder
FROM node:18.16.1-slim as builder
RUN apt-get update && apt-get install -y build-essential python3
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
+4
View File
@@ -17,6 +17,10 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
<div align="center">
📰 <a href="https://ohif.org/news/"><strong>Join OHIF Newsletter</strong></a> 📰
</div>
<div align="center">
📰 <a href="https://ohif.org/news/"><strong>Join OHIF Newsletter</strong></a> 📰
</div>
<hr />
+1 -1
View File
@@ -1 +1 @@
2737903386cf97399473e0fa64fe53ad14da155a
d9258eca70587cf4dc18be4e56c79b16bae73d6d
-134
View File
@@ -1,134 +0,0 @@
import ImageSet from '@ohif/core/src/classes/ImageSet';
import { IWebApiDataSource } from '@ohif/core';
/**
*
*/
export default {
id: '@ohif/extension-*',
/**
* LIFECYCLE HOOKS
*/
preRegistration() {},
beforeExtInit() {},
beforeExtDestroy() {},
/**
* MODULES
*/
getCommandsModule,
getContextModule,
getDataSourcesModule,
getLayoutTemplateModule,
getPanelModule,
getSopClassHandlerModule,
getToolbarModule() {},
getViewportModule,
};
// appConfig,
// extensionConfig,
// dataSources,
// servicesManager,
// extensionManager,
// commandsManager,
/**
*
*/
const getCommandsModule = () => ({
definitions: {
exampleActionDef: {
commandFn: ({ param1 }) => {
console.log(`param1's value is: ${param1}`);
},
// storeContexts: ['viewports'],
options: { param1: 'hello world' },
context: 'VIEWER', // optional
},
},
defaultContext: 'ACTIVE_VIEWPORT::DICOMSR',
});
const ExampleContext = React.createContext();
function ExampleContextProvider({ children }) {
return <ExampleContext.Provider value={{ example: 'value' }}>{children}</ExampleContext.Provider>;
}
const getContextModule = () => [
{
name: 'ExampleContext',
context: ExampleContext,
provider: ExampleContextProvider,
},
];
const getDataSourcesModule = () => [
{
name: 'exampleDataSource',
type: 'webApi', // 'webApi' | 'local' | 'other'
createDataSource: dataSourceConfig => {
return IWebApiDataSource.create(/* */);
},
},
];
const getLayoutTemplateModule = (/* ... */) => [
{
id: 'exampleLayout',
name: 'exampleLayout',
component: ExampleLayoutComponent,
},
];
const getPanelModule = () => {
return [
{
name: 'exampleSidePanel',
iconName: 'info-circle-o',
iconLabel: 'Example',
label: 'Hello World',
isDisabled: studies => {}, // optional
component: ExamplePanelContentComponent,
},
];
};
const getSopClassHandlerModule = (/* ... */) => {
const BASIC_TEXT_SR = '1.2.840.10008.5.1.4.1.1.88.11';
return [
{
name: 'ExampleSopClassHandle',
sopClassUids: [BASIC_TEXT_SR],
getDisplaySetsFromSeries: instances => {
const imageSet = new ImageSet(instances);
imageSet.setAttributes(/** */);
imageSet.sortBy((a, b) => 0);
return imageSet;
},
},
];
};
const getToolbarModule = () => {};
// displaySet, dataSource
const getViewportModule = () => {
const wrappedViewport = props => {
return (
<ExampleViewport
{...props}
onEvent={data => {
commandsManager.runCommand('commandName', data);
}}
/>
);
};
return [{ name: 'example', component: wrappedViewport }];
};
@@ -3,6 +3,313 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
**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.7.0-beta.75",
"version": "3.8.0-beta.2",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -31,10 +31,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.75",
"@ohif/extension-cornerstone": "3.7.0-beta.75",
"@ohif/extension-default": "3.7.0-beta.75",
"@ohif/i18n": "3.7.0-beta.75",
"@ohif/core": "3.8.0-beta.2",
"@ohif/extension-cornerstone": "3.8.0-beta.2",
"@ohif/extension-default": "3.8.0-beta.2",
"@ohif/i18n": "3.8.0-beta.2",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -2,7 +2,6 @@ import { id } from './id';
import React from 'react';
import { Types } from '@ohif/core';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import hydrateRTDisplaySet from './utils/_hydrateRT';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport');
@@ -60,4 +59,3 @@ const extension: Types.Extensions.Extension = {
};
export default extension;
export { hydrateRTDisplaySet };
@@ -1,70 +0,0 @@
async function _hydrateRTDisplaySet({ rtDisplaySet, viewportId, servicesManager }) {
const { segmentationService, hangingProtocolService, viewportGridService } =
servicesManager.services;
const displaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID;
let segmentationId = null;
// We need the hydration to notify panels about the new segmentation added
const suppressEvents = false;
segmentationId = await segmentationService.createSegmentationForRTDisplaySet(
rtDisplaySet,
segmentationId,
suppressEvents
);
segmentationService.hydrateSegmentation(rtDisplaySet.displaySetInstanceUID);
const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportId,
displaySetInstanceUID
);
viewportGridService.setDisplaySetsForViewports(updatedViewports);
// Todo: fix this after we have a better way for stack viewport segmentations
// check every viewport in the viewports to see if the displaySetInstanceUID
// is being displayed, if so we need to update the viewport to use volume viewport
// (if already is not using it) since Cornerstone3D currently only supports
// volume viewport for segmentation
viewports.forEach(viewport => {
if (viewport.viewportId === viewportId) {
return;
}
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
viewport.displaySetInstanceUIDs,
rtDisplaySet.displaySetInstanceUID
);
if (shouldDisplaySeg) {
updatedViewports.push({
viewportId: viewport.viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: {
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
// so we need to re-render (force update via change of the needsRerendering) so that React
// does the diffing and decides we should render this again (although the id and element has not changed)
// so that the CornerstoneViewportService can decide whether to render the segmentation or not.
needsRerendering: true,
initialImageOptions: {
preset: 'middle',
},
},
});
}
});
// Do the entire update at once
viewportGridService.setDisplaySetsForViewports(updatedViewports);
return true;
}
export default _hydrateRTDisplaySet;
@@ -1,7 +1,7 @@
function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {});
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
export default createRTToolGroupAndAddTools;
@@ -1,5 +1,4 @@
import { ButtonEnums } from '@ohif/ui';
import hydrateRTDisplaySet from './_hydrateRT';
const RESPONSE = {
NO_NEVER: -1,
@@ -13,6 +12,7 @@ function promptHydrateRT({
viewportId,
toolGroupId = 'default',
preHydrateCallbacks,
hydrateRTDisplaySet,
}) {
const { uiViewportDialogService } = servicesManager.services;
@@ -3,11 +3,9 @@ import PropTypes from 'prop-types';
import OHIF, { utils } from '@ohif/core';
import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
import _hydrateRTdisplaySet from '../utils/_hydrateRT';
import promptHydrateRT from '../utils/promptHydrateRT';
import _getStatusComponent from './_getStatusComponent';
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
import _hydrateRTDisplaySet from '../utils/_hydrateRT';
const { formatDate } = utils;
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
@@ -95,6 +93,13 @@ function OHIFCornerstoneRTViewport(props) {
});
}, [viewportGrid]);
const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => {
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
displaySets: [rtDisplaySet],
viewportId,
});
};
const getCornerstoneViewport = useCallback(() => {
const { component: Component } = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.viewportModule.cornerstone'
@@ -154,6 +159,7 @@ function OHIFCornerstoneRTViewport(props) {
viewportId,
rtDisplaySet,
preHydrateCallbacks: [storePresentationState],
hydrateRTDisplaySet,
}).then(isHydrated => {
if (isHydrated) {
setIsHydrated(true);
@@ -295,10 +301,9 @@ function OHIFCornerstoneRTViewport(props) {
// presentation state (w/l and invert) and then opens the RT. If we don't store
// the presentation state, the viewport will be reset to the default presentation
storePresentationState();
const isHydrated = await _hydrateRTDisplaySet({
const isHydrated = await hydrateRTDisplaySet({
rtDisplaySet,
viewportId,
servicesManager,
});
setIsHydrated(isHydrated);
@@ -45,10 +45,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
// new MiniCssExtractPlugin({
// filename: `./dist/${outputName}.css`,
// chunkFilename: `./dist/${outputName}.css`,
// }),
new MiniCssExtractPlugin({
filename: `./dist/${outputName}.css`,
chunkFilename: `./dist/${outputName}.css`,
}),
],
});
};
@@ -3,6 +3,332 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
### Bug Fixes
* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332))
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
### Bug Fixes
* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580))
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
### Features
* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65))
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
### Bug Fixes
* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8))
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
### Bug Fixes
* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155))
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
### Bug Fixes
* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e))
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.7.0-beta.75",
"version": "3.8.0-beta.2",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -31,10 +31,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.75",
"@ohif/extension-cornerstone": "3.7.0-beta.75",
"@ohif/extension-default": "3.7.0-beta.75",
"@ohif/i18n": "3.7.0-beta.75",
"@ohif/core": "3.8.0-beta.2",
"@ohif/extension-cornerstone": "3.8.0-beta.2",
"@ohif/extension-default": "3.8.0-beta.2",
"@ohif/i18n": "3.8.0-beta.2",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -44,6 +44,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.20.3",
"@cornerstonejs/tools": "^1.20.3",
"@kitware/vtk.js": "27.3.1",
"react-color": "^2.19.3"
}
}
@@ -0,0 +1,435 @@
import dcmjs from 'dcmjs';
import { createReportDialogPrompt } from '@ohif/extension-default';
import { ServicesManager, Types } from '@ohif/core';
import { cache, metaData } from '@cornerstonejs/core';
import {
segmentation as cornerstoneToolsSegmentation,
Enums as cornerstoneToolsEnums,
} from '@cornerstonejs/tools';
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
import { classes, DicomMetadataStore } from '@ohif/core';
import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchingSquares';
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
import {
updateViewportsForSegmentationRendering,
getUpdatedViewportsForSegmentation,
getTargetViewport,
} from './utils/hydrationUtils';
const { datasetToBlob } = dcmjs.data;
const {
Cornerstone3D: {
Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation },
},
} = adaptersSEG;
const {
Cornerstone3D: {
RTSS: { generateRTSSFromSegmentations },
},
} = adaptersRT;
const { downloadDICOMData } = helpers;
const commandsModule = ({
servicesManager,
extensionManager,
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
const {
uiNotificationService,
segmentationService,
uiDialogService,
displaySetService,
viewportGridService,
} = (servicesManager as ServicesManager).services;
const actions = {
/**
* Retrieves a list of viewports that require updates in preparation for segmentation rendering.
* This function evaluates viewports based on their compatibility with the provided segmentation's
* frame of reference UID and appends them to the updated list if they should render the segmentation.
*
* @param {Object} params - Parameters for the function.
* @param params.viewportId - the ID of the viewport to be updated.
* @param params.servicesManager - The services manager
* @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance.
*
* @returns {Array} Returns an array of viewports that require updates for segmentation rendering.
*/
getUpdatedViewportsForSegmentation,
/**
* Creates an empty segmentation for a specified viewport.
* It first checks if the display set associated with the viewport is reconstructable.
* If not, it raises a notification error. Otherwise, it creates a new segmentation
* for the display set after handling the necessary steps for making the viewport
* a volume viewport first
*
* @param {Object} params - Parameters for the function.
* @param params.viewportId - the target viewport ID.
*
*/
createEmptySegmentationForViewport: async ({ viewportId }) => {
const viewport = getTargetViewport({ viewportId, viewportGridService });
// Todo: add support for multiple display sets
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
if (!displaySet.isReconstructable) {
uiNotificationService.show({
title: 'Segmentation',
message: 'Segmentation is not supported for non-reconstructible displaysets yet',
type: 'error',
});
return;
}
updateViewportsForSegmentationRendering({
viewportId,
servicesManager,
loadFn: async () => {
const currentSegmentations = segmentationService.getSegmentations();
const segmentationId = await segmentationService.createSegmentationForDisplaySet(
displaySetInstanceUID,
{ label: `Segmentation ${currentSegmentations.length + 1}` }
);
const toolGroupId = viewport.viewportOptions.toolGroupId;
await segmentationService.addSegmentationRepresentationToToolGroup(
toolGroupId,
segmentationId
);
// Add only one segment for now
segmentationService.addSegment(segmentationId, {
toolGroupId,
segmentIndex: 1,
properties: {
label: 'Segment 1',
},
});
return segmentationId;
},
});
},
/**
* Loads segmentations for a specified viewport.
* The function prepares the viewport for rendering, then loads the segmentation details.
* Additionally, if the segmentation has scalar data, it is set for the corresponding label map volume.
*
* @param {Object} params - Parameters for the function.
* @param params.segmentations - Array of segmentations to be loaded.
* @param params.viewportId - the target viewport ID.
*
*/
loadSegmentationsForViewport: async ({ segmentations, viewportId }) => {
updateViewportsForSegmentationRendering({
viewportId,
servicesManager,
loadFn: async () => {
// Todo: handle adding more than one segmentation
const viewport = getTargetViewport({ viewportId, viewportGridService });
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
const segmentation = segmentations[0];
const segmentationId = segmentation.id;
const label = segmentation.label;
const segments = segmentation.segments;
delete segmentation.segments;
await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, {
segmentationId,
label,
});
if (segmentation.scalarData) {
const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId);
labelmapVolume.scalarData.set(segmentation.scalarData);
}
segmentationService.addOrUpdateSegmentation(segmentation);
const toolGroupId = viewport.viewportOptions.toolGroupId;
await segmentationService.addSegmentationRepresentationToToolGroup(
toolGroupId,
segmentationId
);
segments.forEach(segment => {
if (segment === null) {
return;
}
segmentationService.addSegment(segmentationId, {
segmentIndex: segment.segmentIndex,
toolGroupId,
properties: {
color: segment.color,
label: segment.label,
opacity: segment.opacity,
isLocked: segment.isLocked,
visibility: segment.isVisible,
active: segmentation.activeSegmentIndex === segment.segmentIndex,
},
});
});
if (segmentation.centroidsIJK) {
segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK);
}
return segmentationId;
},
});
},
/**
* Loads segmentation display sets for a specified viewport.
* Depending on the modality of the display set (SEG or RTSTRUCT),
* it chooses the appropriate service function to create
* the segmentation for the display set.
* The function then prepares the viewport for rendering segmentation.
*
* @param {Object} params - Parameters for the function.
* @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded.
* @param params.displaySets - Array of display sets to be loaded for segmentation.
*
*/
loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => {
// Todo: handle adding more than one segmentation
const displaySet = displaySets[0];
updateViewportsForSegmentationRendering({
viewportId,
servicesManager,
referencedDisplaySetInstanceUID: displaySet.referencedDisplaySetInstanceUID,
loadFn: async () => {
const segDisplaySet = displaySet;
const suppressEvents = false;
const serviceFunction =
segDisplaySet.Modality === 'SEG'
? 'createSegmentationForSEGDisplaySet'
: 'createSegmentationForRTDisplaySet';
const boundFn = segmentationService[serviceFunction].bind(segmentationService);
const segmentationId = await boundFn(segDisplaySet, null, suppressEvents);
return segmentationId;
},
});
},
/**
* Generates a segmentation from a given segmentation ID.
* This function retrieves the associated segmentation and
* its referenced volume, extracts label maps from the
* segmentation volume, and produces segmentation data
* alongside associated metadata.
*
* @param {Object} params - Parameters for the function.
* @param params.segmentationId - ID of the segmentation to be generated.
* @param params.options - Optional configuration for the generation process.
*
* @returns Returns the generated segmentation data.
*/
generateSegmentation: ({ segmentationId, options = {} }) => {
const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId);
const { referencedVolumeId } = segmentation.representationData.LABELMAP;
const segmentationVolume = cache.getVolume(segmentationId);
const referencedVolume = cache.getVolume(referencedVolumeId);
const referencedImages = referencedVolume.getCornerstoneImages();
const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume);
// Generate fake metadata as an example
labelmapObj.metadata = [];
const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => {
// segmentation service already has a color for each segment
const segment = segmentationInOHIF?.segments[segmentIndex];
const { label, color } = segment;
const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
color.slice(0, 3).map(value => value / 255)
).map(value => Math.round(value));
const segmentMetadata = {
SegmentNumber: segmentIndex.toString(),
SegmentLabel: label,
SegmentAlgorithmType: 'MANUAL',
SegmentAlgorithmName: 'OHIF Brush',
RecommendedDisplayCIELabValue,
SegmentedPropertyCategoryCodeSequence: {
CodeValue: 'T-D0050',
CodingSchemeDesignator: 'SRT',
CodeMeaning: 'Tissue',
},
SegmentedPropertyTypeCodeSequence: {
CodeValue: 'T-D0050',
CodingSchemeDesignator: 'SRT',
CodeMeaning: 'Tissue',
},
};
labelmapObj.metadata[segmentIndex] = segmentMetadata;
});
const generatedSegmentation = generateSegmentation(
referencedImages,
labelmapObj,
metaData,
options
);
return generatedSegmentation;
},
/**
* Downloads a segmentation based on the provided segmentation ID.
* This function retrieves the associated segmentation and
* uses it to generate the corresponding DICOM dataset, which
* is then downloaded with an appropriate filename.
*
* @param {Object} params - Parameters for the function.
* @param params.segmentationId - ID of the segmentation to be downloaded.
*
*/
downloadSegmentation: ({ segmentationId }) => {
const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
const generatedSegmentation = actions.generateSegmentation({
segmentationId,
});
downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`);
},
/**
* Stores a segmentation based on the provided segmentationId into a specified data source.
* The SeriesDescription is derived from user input or defaults to the segmentation label,
* and in its absence, defaults to 'Research Derived Series'.
*
* @param {Object} params - Parameters for the function.
* @param params.segmentationId - ID of the segmentation to be stored.
* @param params.dataSource - Data source where the generated segmentation will be stored.
*
* @returns {Object|void} Returns the naturalized report if successfully stored,
* otherwise throws an error.
*/
storeSegmentation: async ({ segmentationId, dataSource }) => {
const promptResult = await createReportDialogPrompt(uiDialogService, {
extensionManager,
});
if (promptResult.action !== 1 && promptResult.value) {
return;
}
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation) {
throw new Error('No segmentation found');
}
const { label } = segmentation;
const SeriesDescription = promptResult.value || label || 'Research Derived Series';
const generatedData = actions.generateSegmentation({
segmentationId,
options: {
SeriesDescription,
},
});
if (!generatedData || !generatedData.dataset) {
throw new Error('Error during segmentation generation');
}
const { dataset: naturalizedReport } = generatedData;
await dataSource.store.dicom(naturalizedReport);
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
// add the information for where we stored it to the instance as well
naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot;
DicomMetadataStore.addInstances([naturalizedReport], true);
return naturalizedReport;
},
/**
* Converts segmentations into RTSS for download.
* This sample function retrieves all segentations and passes to
* cornerstone tool adapter to convert to DICOM RTSS format. It then
* converts dataset to downloadable blob.
*
*/
downloadRTSS: ({ segmentationId }) => {
const segmentations = segmentationService.getSegmentation(segmentationId);
const vtkUtils = {
vtkImageMarchingSquares,
vtkDataArray,
vtkImageData,
};
const RTSS = generateRTSSFromSegmentations(
segmentations,
classes.MetadataProvider,
DicomMetadataStore,
cache,
cornerstoneToolsEnums,
vtkUtils
);
try {
const reportBlob = datasetToBlob(RTSS);
//Create a URL for the binary.
const objectUrl = URL.createObjectURL(reportBlob);
window.location.assign(objectUrl);
} catch (e) {
console.warn(e);
}
},
};
const definitions = {
getUpdatedViewportsForSegmentation: {
commandFn: actions.getUpdatedViewportsForSegmentation,
},
loadSegmentationDisplaySetsForViewport: {
commandFn: actions.loadSegmentationDisplaySetsForViewport,
},
loadSegmentationsForViewport: {
commandFn: actions.loadSegmentationsForViewport,
},
createEmptySegmentationForViewport: {
commandFn: actions.createEmptySegmentationForViewport,
},
generateSegmentation: {
commandFn: actions.generateSegmentation,
},
downloadSegmentation: {
commandFn: actions.downloadSegmentation,
},
storeSegmentation: {
commandFn: actions.storeSegmentation,
},
downloadRTSS: {
commandFn: actions.downloadRTSS,
},
};
return {
actions,
definitions,
};
};
export default commandsModule;
@@ -0,0 +1,70 @@
import React from 'react';
import { useAppConfig } from '@state';
import PanelSegmentation from './panels/PanelSegmentation';
import SegmentationToolbox from './panels/SegmentationToolbox';
const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => {
const { customizationService } = servicesManager.services;
const wrappedPanelSegmentation = configuration => {
const [appConfig] = useAppConfig();
const disableEditingForMode = customizationService.get('segmentation.disableEditing');
return (
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
disableEditing: appConfig.disableEditing || disableEditingForMode?.value,
}}
/>
);
};
const wrappedPanelSegmentationWithTools = configuration => {
const [appConfig] = useAppConfig();
return (
<>
<SegmentationToolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
}}
/>
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
configuration={{
...configuration,
}}
/>
</>
);
};
return [
{
name: 'panelSegmentation',
iconName: 'tab-segmentation',
iconLabel: 'Segmentation',
label: 'Segmentation',
component: wrappedPanelSegmentation,
},
{
name: 'panelSegmentationWithTools',
iconName: 'tab-segmentation',
iconLabel: 'Segmentation',
label: 'Segmentation',
component: wrappedPanelSegmentationWithTools,
},
];
};
export default getPanelModule;
@@ -57,10 +57,11 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
if (!referencedSeriesSequence) {
throw new Error('ReferencedSeriesSequence is missing for the SEG');
console.error('ReferencedSeriesSequence is missing for the SEG');
return;
}
const referencedSeries = referencedSeriesSequence[0];
const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence;
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
+6 -26
View File
@@ -1,12 +1,11 @@
import { id } from './id';
import React from 'react';
import { Types } from '@ohif/core';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import PanelSegmentation from './panels/PanelSegmentation';
import getHangingProtocolModule from './getHangingProtocolModule';
import hydrateSEGDisplaySet from './utils/_hydrateSEG';
import getPanelModule from './getPanelModule';
import getCommandsModule from './commandsModule';
import preRegistration from './init';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport');
@@ -29,6 +28,7 @@ const extension = {
* You ID can be anything you want, but it should be unique.
*/
id,
preRegistration,
/**
* PanelModule should provide a list of panels that will be available in OHIF
@@ -36,27 +36,8 @@ const extension = {
* iconName, iconLabel, label, component} object. Example of a panel module
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
*/
getPanelModule: ({ servicesManager, commandsManager, extensionManager }): Types.Panel[] => {
const wrappedPanelSegmentation = () => {
return (
<PanelSegmentation
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
/>
);
};
return [
{
name: 'panelSegmentation',
iconName: 'tab-segmentation',
iconLabel: 'Segmentation',
label: 'Segmentation',
component: wrappedPanelSegmentation,
},
];
},
getPanelModule,
getCommandsModule,
getViewportModule({ servicesManager, extensionManager }) {
const ExtendedOHIFCornerstoneSEGViewport = props => {
@@ -83,4 +64,3 @@ const extension = {
};
export default extension;
export { hydrateSEGDisplaySet };
@@ -0,0 +1,5 @@
import { addTool, BrushTool } from '@cornerstonejs/tools';
export default function init({ configuration = {} }): void {
addTool(BrushTool);
}
@@ -1,16 +1,22 @@
import { createReportAsync } from '@ohif/extension-default';
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { SegmentationGroupTable } from '@ohif/ui';
import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui';
import callInputDialog from './callInputDialog';
import { useAppConfig } from '@state';
import callColorPickerDialog from './colorPickerDialog';
import { useTranslation } from 'react-i18next';
export default function PanelSegmentation({ servicesManager, commandsManager }) {
const { segmentationService, uiDialogService } = servicesManager.services;
const [appConfig] = useAppConfig();
const disableEditing = appConfig?.disableEditing;
export default function PanelSegmentation({
servicesManager,
commandsManager,
extensionManager,
configuration,
}) {
const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services;
const { t } = useTranslation('PanelSegmentation');
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
const [segmentationConfiguration, setSegmentationConfiguration] = useState(
segmentationService.getConfiguration()
@@ -18,29 +24,6 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
const [isMinimized, setIsMinimized] = useState({});
const onToggleMinimizeSegmentation = useCallback(
id => {
setIsMinimized(prevState => ({
...prevState,
[id]: !prevState[id],
}));
},
[setIsMinimized]
);
// Only expand the last segmentation added to the list and collapse the rest
useEffect(() => {
const lastSegmentationId = segmentations[segmentations.length - 1]?.id;
if (lastSegmentationId) {
setIsMinimized(prevState => ({
...prevState,
[lastSegmentationId]: false,
}));
}
}, [segmentations, setIsMinimized]);
useEffect(() => {
// ~~ Subscription
const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
@@ -64,6 +47,16 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
};
}, []);
const getToolGroupIds = segmentationId => {
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
return toolGroupIds;
};
const onSegmentationAdd = async () => {
commandsManager.runCommand('createEmptySegmentationForViewport');
};
const onSegmentationClick = (segmentationId: string) => {
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
};
@@ -72,14 +65,12 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
segmentationService.remove(segmentationId);
};
const getToolGroupIds = segmentationId => {
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
return toolGroupIds;
const onSegmentAdd = segmentationId => {
segmentationService.addSegment(segmentationId);
};
const onSegmentClick = (segmentationId, segmentIndex) => {
segmentationService.setActiveSegmentForSegmentation(segmentationId, segmentIndex);
segmentationService.setActiveSegment(segmentationId, segmentIndex);
const toolGroupIds = getToolGroupIds(segmentationId);
@@ -101,7 +92,7 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
return;
}
segmentationService.setSegmentLabelForSegmentation(segmentationId, segmentIndex, label);
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
});
};
@@ -126,16 +117,34 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
};
const onSegmentColorClick = (segmentationId, segmentIndex) => {
// Todo: Implement color picker later
return;
const segmentation = segmentationService.getSegmentation(segmentationId);
const segment = segmentation.segments[segmentIndex];
const { color, opacity } = segment;
const rgbaColor = {
r: color[0],
g: color[1],
b: color[2],
a: opacity / 255.0,
};
callColorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
if (actionId === 'cancel') {
return;
}
segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [
newRgbaColor.r,
newRgbaColor.g,
newRgbaColor.b,
newRgbaColor.a * 255.0,
]);
});
};
const onSegmentDelete = (segmentationId, segmentIndex) => {
// segmentationService.removeSegmentFromSegmentation(
// segmentationId,
// segmentIndex
// );
console.warn('not implemented yet');
segmentationService.removeSegment(segmentationId, segmentIndex);
};
const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
@@ -155,6 +164,10 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
});
};
const onToggleSegmentLock = (segmentationId, segmentIndex) => {
segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
};
const onToggleSegmentationVisibility = segmentationId => {
segmentationService.toggleSegmentationVisibility(segmentationId);
};
@@ -169,27 +182,69 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
[segmentationService]
);
const onSegmentationDownload = segmentationId => {
commandsManager.runCommand('downloadSegmentation', {
segmentationId,
});
};
const storeSegmentation = async segmentationId => {
const datasources = extensionManager.getActiveDataSource();
const displaySetInstanceUIDs = await createReportAsync({
servicesManager,
getReport: () =>
commandsManager.runCommand('storeSegmentation', {
segmentationId,
dataSource: datasources[0],
}),
reportType: 'Segmentation',
});
// Show the exported report in the active viewport as read only (similar to SR)
if (displaySetInstanceUIDs) {
// clear the segmentation that we exported, similar to the storeMeasurement
// where we remove the measurements and prompt again the user if they would like
// to re-read the measurements in a SR read only viewport
segmentationService.remove(segmentationId);
viewportGridService.setDisplaySetsForViewport({
viewportId: viewportGridService.getActiveViewportId(),
displaySetInstanceUIDs,
});
}
};
const onSegmentationDownloadRTSS = segmentationId => {
commandsManager.runCommand('downloadRTSS', {
segmentationId,
});
};
return (
<div className="mt-1 flex min-h-0 flex-auto flex-col justify-between">
{/* show segmentation table */}
{segmentations?.length ? (
<>
<div className="ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto">
<SegmentationGroupTable
title={t('Segmentations')}
showAddSegmentation={false}
segmentations={segmentations}
isMinimized={isMinimized}
disableEditing={configuration.disableEditing}
activeSegmentationId={selectedSegmentationId || ''}
onSegmentationAdd={onSegmentationAdd}
onSegmentationClick={onSegmentationClick}
onSegmentationDelete={onSegmentationDelete}
onSegmentationDownload={onSegmentationDownload}
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
storeSegmentation={storeSegmentation}
onSegmentationEdit={onSegmentationEdit}
onSegmentClick={onSegmentClick}
onSegmentEdit={onSegmentEdit}
disableEditing={disableEditing}
onSegmentAdd={onSegmentAdd}
onSegmentColorClick={onSegmentColorClick}
onSegmentDelete={onSegmentDelete}
onToggleSegmentVisibility={onToggleSegmentVisibility}
onToggleSegmentLock={onToggleSegmentLock}
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
onToggleMinimizeSegmentation={onToggleMinimizeSegmentation}
showDeleteSegment={true}
segmentationConfig={{ initialConfig: segmentationConfiguration }}
setRenderOutline={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value)
@@ -217,8 +272,8 @@ export default function PanelSegmentation({ servicesManager, commandsManager })
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
}
/>
) : null}
</div>
</div>
</>
);
}
@@ -0,0 +1,405 @@
import React, { useCallback, useEffect, useState, useReducer } from 'react';
import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui';
import { Types } from '@ohif/extension-cornerstone';
import { utilities } from '@cornerstonejs/tools';
const { segmentation: segmentationUtils } = utilities;
const TOOL_TYPES = {
CIRCULAR_BRUSH: 'CircularBrush',
SPHERE_BRUSH: 'SphereBrush',
CIRCULAR_ERASER: 'CircularEraser',
SPHERE_ERASER: 'SphereEraser',
CIRCLE_SHAPE: 'CircleScissor',
RECTANGLE_SHAPE: 'RectangleScissor',
SPHERE_SHAPE: 'SphereScissor',
THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush',
THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush',
};
const ACTIONS = {
SET_TOOL_CONFIG: 'SET_TOOL_CONFIG',
SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL',
};
const initialState = {
Brush: {
brushSize: 15,
mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush'
},
Eraser: {
brushSize: 15,
mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser'
},
Shapes: {
brushSize: 15,
mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor'
},
ThresholdBrush: {
brushSize: 15,
thresholdRange: [-500, 500],
},
activeTool: null,
};
function toolboxReducer(state, action) {
switch (action.type) {
case ACTIONS.SET_TOOL_CONFIG:
const { tool, config } = action.payload;
return {
...state,
[tool]: {
...state[tool],
...config,
},
};
case ACTIONS.SET_ACTIVE_TOOL:
return { ...state, activeTool: action.payload };
default:
return state;
}
}
function SegmentationToolbox({ servicesManager, extensionManager }) {
const { toolbarService, segmentationService, toolGroupService } =
servicesManager.services as Types.CornerstoneServices;
const [viewportGrid] = useViewportGrid();
const { viewports, activeViewportId } = viewportGrid;
const [toolsEnabled, setToolsEnabled] = useState(false);
const [state, dispatch] = useReducer(toolboxReducer, initialState);
const updateActiveTool = useCallback(() => {
if (!viewports?.size || activeViewportId === undefined) {
return;
}
const viewport = viewports.get(activeViewportId);
if (!viewport) {
return;
}
dispatch({
type: ACTIONS.SET_ACTIVE_TOOL,
payload: toolGroupService.getActiveToolForViewport(viewport.viewportId),
});
}, [activeViewportId, viewports, toolGroupService, dispatch]);
const setToolActive = useCallback(
toolName => {
toolbarService.recordInteraction({
interactionType: 'tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: {
toolName,
},
},
],
});
dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName });
},
[toolbarService, dispatch]
);
/**
* sets the tools enabled IF there are segmentations
*/
useEffect(() => {
const events = [
segmentationService.EVENTS.SEGMENTATION_ADDED,
segmentationService.EVENTS.SEGMENTATION_UPDATED,
segmentationService.EVENTS.SEGMENTATION_REMOVED,
];
const unsubscriptions = [];
events.forEach(event => {
const { unsubscribe } = segmentationService.subscribe(event, () => {
const segmentations = segmentationService.getSegmentations();
const activeSegmentation = segmentations?.find(seg => seg.isActive);
setToolsEnabled(activeSegmentation?.segmentCount > 0);
});
unsubscriptions.push(unsubscribe);
});
updateActiveTool();
return () => {
unsubscriptions.forEach(unsubscribe => unsubscribe());
};
}, [activeViewportId, viewports, segmentationService, updateActiveTool]);
/**
* Update the active tool when the toolbar state changes
*/
useEffect(() => {
const { unsubscribe } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
() => {
updateActiveTool();
}
);
return () => {
unsubscribe();
};
}, [toolbarService, updateActiveTool]);
useEffect(() => {
// if the active tool is not a brush tool then do nothing
if (!Object.values(TOOL_TYPES).includes(state.activeTool)) {
return;
}
// if the tool is Segmentation and it is enabled then do nothing
if (toolsEnabled) {
return;
}
// if the tool is Segmentation and it is disabled, then switch
// back to the window level tool to not confuse the user when no
// segmentation is active or when there is no segment in the segmentation
setToolActive('WindowLevel');
}, [toolsEnabled, state.activeTool, setToolActive]);
const updateBrushSize = useCallback(
(toolName, brushSize) => {
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
});
},
[toolGroupService]
);
const onBrushSizeChange = useCallback(
(valueAsStringOrNumber, toolCategory) => {
const value = Number(valueAsStringOrNumber);
_getToolNamesFromCategory(toolCategory).forEach(toolName => {
updateBrushSize(toolName, value);
});
dispatch({
type: ACTIONS.SET_TOOL_CONFIG,
payload: {
tool: toolCategory,
config: { brushSize: value },
},
});
},
[toolGroupService, dispatch]
);
const handleRangeChange = useCallback(
newRange => {
if (
newRange[0] === state.ThresholdBrush.thresholdRange[0] &&
newRange[1] === state.ThresholdBrush.thresholdRange[1]
) {
return;
}
const toolNames = _getToolNamesFromCategory('ThresholdBrush');
toolNames.forEach(toolName => {
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
toolGroup.setToolConfiguration(toolName, {
strategySpecificConfiguration: {
THRESHOLD_INSIDE_CIRCLE: {
threshold: newRange,
},
},
});
});
});
dispatch({
type: ACTIONS.SET_TOOL_CONFIG,
payload: {
tool: 'ThresholdBrush',
config: { thresholdRange: newRange },
},
});
},
[toolGroupService, dispatch, state.ThresholdBrush.thresholdRange]
);
return (
<AdvancedToolbox
title="Segmentation Tools"
items={[
{
name: 'Brush',
icon: 'icon-tool-brush',
disabled: !toolsEnabled,
active:
state.activeTool === TOOL_TYPES.CIRCULAR_BRUSH ||
state.activeTool === TOOL_TYPES.SPHERE_BRUSH,
onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_BRUSH),
options: [
{
name: 'Radius (mm)',
id: 'brush-radius',
type: 'range',
min: 0.5,
max: 99.5,
value: state.Brush.brushSize,
step: 0.5,
onChange: value => onBrushSizeChange(value, 'Brush'),
},
{
name: 'Mode',
type: 'radio',
id: 'brush-mode',
value: state.Brush.mode,
values: [
{ value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' },
{ value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' },
],
onChange: value => setToolActive(value),
},
],
},
{
name: 'Eraser',
icon: 'icon-tool-eraser',
disabled: !toolsEnabled,
active:
state.activeTool === TOOL_TYPES.CIRCULAR_ERASER ||
state.activeTool === TOOL_TYPES.SPHERE_ERASER,
onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER),
options: [
{
name: 'Radius (mm)',
type: 'range',
id: 'eraser-radius',
min: 0.5,
max: 99.5,
value: state.Eraser.brushSize,
step: 0.5,
onChange: value => onBrushSizeChange(value, 'Eraser'),
},
{
name: 'Mode',
type: 'radio',
id: 'eraser-mode',
value: state.Eraser.mode,
values: [
{ value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' },
{ value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' },
],
onChange: value => setToolActive(value),
},
],
},
{
name: 'Shapes',
icon: 'icon-tool-shape',
disabled: !toolsEnabled,
active:
state.activeTool === TOOL_TYPES.CIRCLE_SHAPE ||
state.activeTool === TOOL_TYPES.RECTANGLE_SHAPE ||
state.activeTool === TOOL_TYPES.SPHERE_SHAPE,
onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SHAPE),
options: [
{
name: 'Mode',
type: 'radio',
value: state.Shapes.mode,
id: 'shape-mode',
values: [
{ value: TOOL_TYPES.CIRCLE_SHAPE, label: 'Circle' },
{ value: TOOL_TYPES.RECTANGLE_SHAPE, label: 'Rectangle' },
{ value: TOOL_TYPES.SPHERE_SHAPE, label: 'Sphere' },
],
onChange: value => setToolActive(value),
},
],
},
{
name: 'Threshold Tool',
icon: 'icon-tool-threshold',
disabled: !toolsEnabled,
active:
state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH ||
state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH,
onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH),
options: [
{
name: 'Radius (mm)',
id: 'threshold-radius',
type: 'range',
min: 0.5,
max: 99.5,
value: state.ThresholdBrush.brushSize,
step: 0.5,
onChange: value => onBrushSizeChange(value, 'ThresholdBrush'),
},
{
name: 'Mode',
type: 'radio',
id: 'threshold-mode',
value: state.activeTool,
values: [
{ value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' },
{ value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' },
],
onChange: value => setToolActive(value),
},
{
type: 'custom',
id: 'segmentation-threshold-range',
children: () => {
return (
<div>
<div className="bg-secondary-light h-[1px]"></div>
<div className="mt-1 text-[13px] text-white">Threshold</div>
<InputDoubleRange
values={state.ThresholdBrush.thresholdRange}
onChange={handleRangeChange}
minValue={-1000}
maxValue={1000}
step={1}
showLabel={true}
allowNumberEdit={true}
showAdjustmentArrows={false}
/>
</div>
);
},
},
],
},
]}
/>
);
}
function _getToolNamesFromCategory(category) {
let toolNames = [];
switch (category) {
case 'Brush':
toolNames = ['CircularBrush', 'SphereBrush'];
break;
case 'Eraser':
toolNames = ['CircularEraser', 'SphereEraser'];
break;
case 'ThresholdBrush':
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'];
break;
default:
break;
}
return toolNames;
}
export default SegmentationToolbox;
@@ -0,0 +1,3 @@
.chrome-picker {
background: #090c29 !important;
}
@@ -0,0 +1,58 @@
import React from 'react';
import { Dialog } from '@ohif/ui';
import { ChromePicker } from 'react-color';
import './colorPickerDialog.css';
function callColorPickerDialog(uiDialogService, rgbaColor, callback) {
const dialogId = 'pick-color';
const onSubmitHandler = ({ action, value }) => {
switch (action.id) {
case 'save':
callback(value.rgbaColor, action.id);
break;
case 'cancel':
callback('', action.id);
break;
}
uiDialogService.dismiss({ id: dialogId });
};
if (uiDialogService) {
uiDialogService.create({
id: dialogId,
centralize: true,
isDraggable: false,
showOverlay: true,
content: Dialog,
contentProps: {
title: 'Segment Color',
value: { rgbaColor },
noCloseButton: true,
onClose: () => uiDialogService.dismiss({ id: dialogId }),
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
],
onSubmit: onSubmitHandler,
body: ({ value, setValue }) => {
const handleChange = color => {
setValue({ rgbaColor: color.rgb });
};
return (
<ChromePicker
color={value.rgbaColor}
onChange={handleChange}
presetColors={[]}
width={300}
/>
);
},
},
});
}
}
export default callColorPickerDialog;
@@ -1,73 +0,0 @@
async function _hydrateSEGDisplaySet({
segDisplaySet,
viewportId: targetViewportId,
servicesManager,
}) {
const { segmentationService, hangingProtocolService, viewportGridService } =
servicesManager.services;
const displaySetInstanceUID = segDisplaySet.referencedDisplaySetInstanceUID;
let segmentationId = null;
// We need the hydration to notify panels about the new segmentation added
const suppressEvents = false;
segmentationId = await segmentationService.createSegmentationForSEGDisplaySet(
segDisplaySet,
segmentationId,
suppressEvents
);
segmentationService.hydrateSegmentation(segDisplaySet.displaySetInstanceUID);
const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
targetViewportId,
displaySetInstanceUID
);
// Todo: fix this after we have a better way for stack viewport segmentations
// check every viewport in the viewports to see if the displaySetInstanceUID
// is being displayed, if so we need to update the viewport to use volume viewport
// (if already is not using it) since Cornerstone3D currently only supports
// volume viewport for segmentation
viewports.forEach((viewport, viewportId) => {
if (targetViewportId === viewportId) {
return;
}
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
viewport.displaySetInstanceUIDs,
segDisplaySet.displaySetInstanceUID
);
if (shouldDisplaySeg) {
updatedViewports.push({
viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: {
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
// so we need to re-render (force update via change of the needsRerendering) so that React
// does the diffing and decides we should render this again (although the id and element has not changed)
// so that the CornerstoneViewportService can decide whether to render the segmentation or not.
needsRerendering: true,
initialImageOptions: {
preset: 'middle',
},
},
});
}
});
// Do the entire update at once
viewportGridService.setDisplaySetsForViewports(updatedViewports);
return true;
}
export default _hydrateSEGDisplaySet;
@@ -0,0 +1,190 @@
import { Enums, cache } from '@cornerstonejs/core';
/**
* Updates the viewports in preparation for rendering segmentations.
* Evaluates each viewport to determine which need modifications,
* then for those viewports, changes them to a volume type and ensures
* they are ready for segmentation rendering.
*
* @param {Object} params - Parameters for the function.
* @param params.viewportId - ID of the viewport to be updated.
* @param params.loadFn - Function to load the segmentation data.
* @param params.servicesManager - The services manager.
* @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance.
*
* @returns Returns true upon successful update of viewports for segmentation rendering.
*/
async function updateViewportsForSegmentationRendering({
viewportId,
loadFn,
servicesManager,
referencedDisplaySetInstanceUID,
}: {
viewportId: string;
loadFn: () => Promise<string>;
servicesManager: any;
referencedDisplaySetInstanceUID?: string;
}) {
const { cornerstoneViewportService, segmentationService, viewportGridService } =
servicesManager.services;
const viewport = getTargetViewport({ viewportId, viewportGridService });
const targetViewportId = viewport.viewportOptions.viewportId;
referencedDisplaySetInstanceUID =
referencedDisplaySetInstanceUID || viewport?.displaySetInstanceUIDs[0];
const updatedViewports = getUpdatedViewportsForSegmentation({
servicesManager,
viewportId,
referencedDisplaySetInstanceUID,
});
// create Segmentation callback which needs to be waited until
// the volume is created (if coming from stack)
const createSegmentationForVolume = async () => {
const segmentationId = await loadFn();
segmentationService.hydrateSegmentation(segmentationId);
};
// the reference volume that is used to draw the segmentation. so check if the
// volume exists in the cache (the target Viewport is already a volume viewport)
const volumeExists = Array.from(cache._volumeCache.keys()).some(volumeId =>
volumeId.includes(referencedDisplaySetInstanceUID)
);
updatedViewports.forEach(async viewport => {
viewport.viewportOptions = {
...viewport.viewportOptions,
viewportType: 'volume',
needsRerendering: true,
};
const viewportId = viewport.viewportId;
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const prevCamera = csViewport.getCamera();
// only run the createSegmentationForVolume for the targetViewportId
// since the rest will get handled by cornerstoneViewportService
if (volumeExists && viewportId === targetViewportId) {
await createSegmentationForVolume();
return;
}
const createNewSegmentationWhenVolumeMounts = async evt => {
const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac =>
ac.uid.includes(referencedDisplaySetInstanceUID)
);
// Note: make sure to re-grab the viewport since it might have changed
// during the time it took for the volume to be mounted, for instance
// the stack viewport has been changed to a volume viewport
const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
volumeViewport.setCamera(prevCamera);
volumeViewport.element.removeEventListener(
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
createNewSegmentationWhenVolumeMounts
);
if (!isTheActiveViewportVolumeMounted) {
// it means it is one of those other updated viewports so just update the camera
return;
}
if (viewportId === targetViewportId) {
await createSegmentationForVolume();
}
};
csViewport.element.addEventListener(
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
createNewSegmentationWhenVolumeMounts
);
});
// Set the displaySets for the viewports that require to be updated
viewportGridService.setDisplaySetsForViewports(updatedViewports);
return true;
}
const getTargetViewport = ({ viewportId, viewportGridService }) => {
const { viewports, activeViewportId } = viewportGridService.getState();
const targetViewportId = viewportId || activeViewportId;
const viewport = viewports.get(targetViewportId);
return viewport;
};
/**
* Retrieves a list of viewports that require updates in preparation for segmentation rendering.
* This function evaluates viewports based on their compatibility with the provided segmentation's
* frame of reference UID and appends them to the updated list if they should render the segmentation.
*
* @param {Object} params - Parameters for the function.
* @param params.viewportId - the ID of the viewport to be updated.
* @param params.servicesManager - The services manager
* @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance.
*
* @returns {Array} Returns an array of viewports that require updates for segmentation rendering.
*/
function getUpdatedViewportsForSegmentation({
viewportId,
servicesManager,
referencedDisplaySetInstanceUID,
}) {
const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } =
servicesManager.services;
const { viewports } = viewportGridService.getState();
const viewport = getTargetViewport({ viewportId, viewportGridService });
const targetViewportId = viewport.viewportOptions.viewportId;
const displaySetInstanceUIDs = viewports.get(targetViewportId).displaySetInstanceUIDs;
const referenceDisplaySetInstanceUID =
referencedDisplaySetInstanceUID || displaySetInstanceUIDs[0];
const referencedDisplaySet = displaySetService.getDisplaySetByUID(referenceDisplaySetInstanceUID);
const segmentationFrameOfReferenceUID = referencedDisplaySet.instances[0].FrameOfReferenceUID;
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
targetViewportId,
referenceDisplaySetInstanceUID
);
viewports.forEach((viewport, viewportId) => {
if (
targetViewportId === viewportId ||
updatedViewports.find(v => v.viewportId === viewportId)
) {
return;
}
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
viewport.displaySetInstanceUIDs,
segmentationFrameOfReferenceUID
);
if (shouldDisplaySeg) {
updatedViewports.push({
viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: {
viewportType: 'volume',
needsRerendering: true,
},
});
}
});
return updatedViewports;
}
export {
updateViewportsForSegmentationRendering,
getUpdatedViewportsForSegmentation,
getTargetViewport,
};
@@ -1,7 +1,7 @@
function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {});
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
export default createSEGToolGroupAndAddTools;
@@ -1,5 +1,4 @@
import { ButtonEnums } from '@ohif/ui';
import hydrateSEGDisplaySet from './_hydrateSEG';
const RESPONSE = {
NO_NEVER: -1,
@@ -7,7 +6,13 @@ const RESPONSE = {
HYDRATE_SEG: 5,
};
function promptHydrateSEG({ servicesManager, segDisplaySet, viewportId, preHydrateCallbacks }) {
function promptHydrateSEG({
servicesManager,
segDisplaySet,
viewportId,
preHydrateCallbacks,
hydrateSEGDisplaySet,
}) {
const { uiViewportDialogService } = servicesManager.services;
return new Promise(async function (resolve, reject) {
@@ -21,7 +26,6 @@ function promptHydrateSEG({ servicesManager, segDisplaySet, viewportId, preHydra
const isHydrated = await hydrateSEGDisplaySet({
segDisplaySet,
viewportId,
servicesManager,
});
resolve(isHydrated);
@@ -5,7 +5,6 @@ import OHIF, { utils } from '@ohif/core';
import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui';
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG';
import hydrateSEGDisplaySet from '../utils/_hydrateSEG';
import _getStatusComponent from './_getStatusComponent';
const { formatDate } = utils;
@@ -158,6 +157,7 @@ function OHIFCornerstoneSEGViewport(props) {
viewportId,
segDisplaySet,
preHydrateCallbacks: [storePresentationState],
hydrateSEGDisplaySet,
}).then(isHydrated => {
if (isHydrated) {
setIsHydrated(true);
@@ -291,6 +291,13 @@ function OHIFCornerstoneSEGViewport(props) {
SpacingBetweenSlices,
} = referencedDisplaySetRef.current.metadata;
const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => {
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
displaySets: [segDisplaySet],
viewportId,
});
};
const onStatusClick = async () => {
// Before hydrating a SEG and make it added to all viewports in the grid
// that share the same frameOfReferenceUID, we need to store the viewport grid
@@ -302,7 +309,6 @@ function OHIFCornerstoneSEGViewport(props) {
const isHydrated = await hydrateSEGDisplaySet({
segDisplaySet,
viewportId,
servicesManager,
});
setIsHydrated(isHydrated);
@@ -369,12 +375,18 @@ OHIFCornerstoneSEGViewport.defaultProps = {
};
function _getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) {
const {
SharedFunctionalGroupsSequence: [SharedFunctionalGroup],
} = segDisplaySet.instance;
const {
PixelMeasuresSequence: [PixelMeasures],
} = SharedFunctionalGroup;
const { SharedFunctionalGroupsSequence } = segDisplaySet.instance;
const SharedFunctionalGroup = Array.isArray(SharedFunctionalGroupsSequence)
? SharedFunctionalGroupsSequence[0]
: SharedFunctionalGroupsSequence;
const { PixelMeasuresSequence } = SharedFunctionalGroup;
const PixelMeasures = Array.isArray(PixelMeasuresSequence)
? PixelMeasuresSequence[0]
: PixelMeasuresSequence;
const { SpacingBetweenSlices, SliceThickness } = PixelMeasures;
const image0 = referencedDisplaySet.images[0];
@@ -3,6 +3,331 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
### Bug Fixes
* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332))
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
### Bug Fixes
* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580))
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
### Features
* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65))
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
### Bug Fixes
* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155))
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
### Bug Fixes
* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e))
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
### Performance Improvements
* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab))
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
**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.7.0-beta.75",
"version": "3.8.0-beta.2",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -32,10 +32,10 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.75",
"@ohif/extension-cornerstone": "3.7.0-beta.75",
"@ohif/extension-measurement-tracking": "3.7.0-beta.75",
"@ohif/ui": "3.7.0-beta.75",
"@ohif/core": "3.8.0-beta.2",
"@ohif/extension-cornerstone": "3.8.0-beta.2",
"@ohif/extension-measurement-tracking": "3.8.0-beta.2",
"@ohif/ui": "3.8.0-beta.2",
"dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -44,9 +44,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.13.2",
"@cornerstonejs/core": "^1.13.2",
"@cornerstonejs/tools": "^1.13.2",
"@cornerstonejs/adapters": "^1.20.3",
"@cornerstonejs/core": "^1.20.3",
"@cornerstonejs/tools": "^1.20.3",
"classnames": "^2.3.2"
}
}
@@ -240,7 +240,7 @@ function OHIFCornerstoneSRViewport(props) {
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
({ displaySetInstanceUIDs }) => {
const activeViewport = viewports[activeViewportId];
const activeViewport = viewports.get(activeViewportId);
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
viewportGridService.setDisplaySetsForViewport({
viewportId: activeViewportId,
+356
View File
@@ -3,6 +3,362 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
### Bug Fixes
* **cine:** Use the frame rate specified in DICOM and optionally auto play cine ([#3735](https://github.com/OHIF/Viewers/issues/3735)) ([d9258ec](https://github.com/OHIF/Viewers/commit/d9258eca70587cf4dc18be4e56c79b16bae73d6d))
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
### Bug Fixes
* **calibration:** No calibration popup caused by perhaps an unused code optimization for production builds ([#3736](https://github.com/OHIF/Viewers/issues/3736)) ([93d798d](https://github.com/OHIF/Viewers/commit/93d798db99c0dee53ef73c376f8a74ac3049cf3f))
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
### Bug Fixes
* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8))
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
### Bug Fixes
* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe))
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
### Bug Fixes
* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332))
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
### Bug Fixes
* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580))
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
### Bug Fixes
* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339))
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
### Features
* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65))
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
### Bug Fixes
* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07))
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
### Bug Fixes
* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155))
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
### Features
* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799))
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
### Features
* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89))
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
### Bug Fixes
* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96))
* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9))
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
### Bug Fixes
* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e))
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
### Bug Fixes
* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5))
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
### Performance Improvements
* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab))
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
**Note:** Version bump only for package @ohif/extension-cornerstone
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.7.0-beta.75",
"version": "3.8.0-beta.2",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -36,9 +36,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.13.2",
"@ohif/core": "3.7.0-beta.75",
"@ohif/ui": "3.7.0-beta.75",
"@cornerstonejs/dicom-image-loader": "^1.20.3",
"@ohif/core": "3.8.0-beta.2",
"@ohif/ui": "3.8.0-beta.2",
"dcmjs": "^0.29.6",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -52,10 +52,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.13.2",
"@cornerstonejs/core": "^1.13.2",
"@cornerstonejs/streaming-image-volume-loader": "^1.13.2",
"@cornerstonejs/tools": "^1.13.2",
"@cornerstonejs/adapters": "^1.20.3",
"@cornerstonejs/core": "^1.20.3",
"@cornerstonejs/streaming-image-volume-loader": "^1.20.3",
"@cornerstonejs/tools": "^1.20.3",
"@kitware/vtk.js": "27.3.1",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",
+38 -23
View File
@@ -18,7 +18,6 @@ import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import { CornerstoneServices } from './types';
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
function commandsModule({
servicesManager,
@@ -29,7 +28,6 @@ function commandsModule({
toolGroupService,
cineService,
toolbarService,
stateSyncService,
uiDialogService,
cornerstoneViewportService,
uiNotificationService,
@@ -156,7 +154,7 @@ function commandsModule({
* @param props - containing the updates to apply
* @param props.measurementKey - chooses the measurement key to apply the
* code to. This will typically be finding or site to apply a
* finind code or a findingSites code.
* finding code or a findingSites code.
* @param props.code - A coding scheme value from DICOM, including:
* * CodeValue - the language independent code, for example '1234'
* * CodingSchemeDesignator - the issue of the code value
@@ -226,6 +224,25 @@ function commandsModule({
arrowTextCallback: ({ callback, data }) => {
callInputDialog(uiDialogService, data, callback);
},
cleanUpCrosshairs: () => {
// if the crosshairs tool is active, deactivate it and set window level active
// since we are going back to main non-mpr HP
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
if (activeViewportToolGroup._toolInstances?.Crosshairs?.mode === Enums.ToolModes.Active) {
actions.toolbarServiceRecordInteraction({
interactionType: 'tool',
commands: [
{
commandOptions: {
toolName: 'WindowLevel',
},
context: 'CORNERSTONE',
},
],
});
}
},
toggleCine: () => {
const { viewports } = viewportGridService.getState();
const { isCineEnabled } = cineService.getState();
@@ -266,7 +283,11 @@ function commandsModule({
toolbarServiceRecordInteraction: props => {
toolbarService.recordInteraction(props);
},
// Enable or disable a toggleable command, without calling the activation
// Used to setup already active tools from hanging protocols
setToolbarToggled: props => {
toolbarService.setToggled(props.toolId, props.isActive ?? true);
},
setToolActive: ({ toolName, toolGroupId = null, toggledState }) => {
if (toolName === 'Crosshairs') {
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
@@ -291,18 +312,8 @@ function commandsModule({
}
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
const toolGroupViewportIds = toolGroup?.getViewportIds?.();
// if toolGroup has been destroyed, or its viewports have been removed
if (!toolGroupViewportIds || !toolGroupViewportIds.length) {
return;
}
const filteredViewports = Array.from(viewports.values()).filter(viewport => {
return toolGroupViewportIds.includes(viewport.viewportId);
});
if (!filteredViewports.length) {
if (!toolGroup) {
return;
}
@@ -449,7 +460,7 @@ function commandsModule({
viewport.resetProperties();
viewport.resetCamera();
} else {
// Todo: add reset properties for volume viewport
viewport.resetProperties();
viewport.resetCamera();
}
@@ -551,16 +562,16 @@ function commandsModule({
toggleStackImageSync: ({ toggledState }) => {
toggleStackImageSync({
getEnabledElement,
servicesManager,
toggledState,
});
},
setSourceViewportForReferenceLinesTool: ({ toggledState }) => {
const { activeViewportId } = viewportGridService.getState();
const viewportInfo = cornerstoneViewportService.getViewportInfo(activeViewportId);
setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => {
if (!viewportId) {
const { activeViewportId } = viewportGridService.getState();
viewportId = activeViewportId;
}
const viewportId = viewportInfo.getViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
toolGroup.setToolConfiguration(
@@ -701,8 +712,12 @@ function commandsModule({
},
storePresentation: {
commandFn: actions.storePresentation,
storeContexts: [],
options: {},
},
setToolbarToggled: {
commandFn: actions.setToolbarToggled,
},
cleanUpCrosshairs: {
commandFn: actions.cleanUpCrosshairs,
},
};
@@ -1,11 +1,19 @@
import React, { useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
import { Enums, eventTarget } from '@cornerstonejs/core';
import { useAppConfig } from '@state';
function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
const { toolbarService, customizationService } = servicesManager.services;
const [{ isCineEnabled, cines }, cineService] = useCine();
const [{ activeViewportId }] = useViewportGrid();
const {
toolbarService,
customizationService,
displaySetService,
viewportGridService,
cineService,
} = servicesManager.services;
const [{ isCineEnabled, cines }] = useCine();
const [newStackFrameRate, setNewStackFrameRate] = useState(24);
const [appConfig] = useAppConfig();
const { component: CinePlayerComponent = CinePlayer } =
customizationService.get('cinePlayer') ?? {};
@@ -13,12 +21,12 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
const handleCineClose = () => {
toolbarService.recordInteraction({
groupId: 'MoreTools',
itemId: 'cine',
interactionType: 'toggle',
commands: [
{
commandName: 'toggleCine',
commandOptions: {},
toolName: 'cine',
context: 'CORNERSTONE',
},
],
@@ -45,14 +53,37 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
}
};
const newStackCineHandler = useCallback(() => {
const { viewports } = viewportGridService.getState();
const { displaySetInstanceUIDs } = viewports.get(viewportId);
let frameRate = 24;
let isPlaying = cines[viewportId].isPlaying;
displaySetInstanceUIDs.forEach(displaySetInstanceUID => {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
if (displaySet.FrameRate) {
// displaySet.FrameRate corresponds to DICOM tag (0018,1063) which is defined as the the frame time in milliseconds
// So a bit of math to get the actual frame rate.
frameRate = Math.round(1000 / displaySet.FrameRate);
isPlaying ||= !!appConfig.autoPlayCine;
}
});
if (isPlaying) {
cineService.setIsCineEnabled(isPlaying);
}
cineService.setCine({ id: viewportId, isPlaying, frameRate });
setNewStackFrameRate(frameRate);
}, [cineService, displaySetService, viewportId, viewportGridService, cines]);
useEffect(() => {
eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler);
return () => {
cineService.setCine({ id: viewportId, isPlaying: false });
eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler);
};
}, [enabledVPElement]);
}, [enabledVPElement, newStackCineHandler]);
useEffect(() => {
if (!cines || !cines[viewportId] || !enabledVPElement) {
@@ -75,17 +106,18 @@ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
isCineEnabled && (
<CinePlayerComponent
className="absolute left-1/2 bottom-3 -translate-x-1/2"
frameRate={newStackFrameRate}
isPlaying={isPlaying}
onClose={handleCineClose}
onPlayPauseChange={isPlaying =>
cineService.setCine({
id: activeViewportId,
id: viewportId,
isPlaying,
})
}
onFrameRateChange={frameRate =>
cineService.setCine({
id: activeViewportId,
id: viewportId,
frameRate,
})
}
@@ -24,18 +24,7 @@ const mpr: Types.HangingProtocol.Protocol = {
// Turns off crosshairs when switching out of MPR mode
onProtocolExit: [
{
commandName: 'toolbarServiceRecordInteraction',
commandOptions: {
interactionType: 'tool',
commands: [
{
commandOptions: {
toolName: 'WindowLevel',
},
context: 'CORNERSTONE',
},
],
},
commandName: 'cleanUpCrosshairs',
},
],
},
+1 -1
View File
@@ -137,7 +137,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
export type { PublicViewportOptions };
export {
measurementMappingUtils,
CornerstoneExtensionTypes,
CornerstoneExtensionTypes as Types,
toolNames,
getActiveViewportEnabledElement,
};
+79 -34
View File
@@ -12,8 +12,9 @@ import {
imageLoadPoolManager,
Settings,
utilities as csUtilities,
Enums as csEnums,
} from '@cornerstonejs/core';
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
import { Enums } from '@cornerstonejs/tools';
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
import initWADOImageLoader from './initWADOImageLoader';
@@ -27,6 +28,7 @@ import interleaveTopToBottom from './utils/interleaveTopToBottom';
import initContextMenu from './initContextMenu';
import initDoubleClick from './initDoubleClick';
import { CornerstoneServices } from './types';
import initViewTiming from './utils/initViewTiming';
// TODO: Cypress tests are currently grabbing this from the window?
window.cornerstone = cornerstone;
@@ -41,10 +43,30 @@ export default async function init({
configuration,
appConfig,
}: Types.Extensions.ExtensionParams): Promise<void> {
await cs3DInit();
// Note: this should run first before initializing the cornerstone
// DO NOT CHANGE THE ORDER
const value = appConfig.useSharedArrayBuffer;
let sharedArrayBufferDisabled = false;
if (value === 'AUTO') {
cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.AUTO);
} else if (value === 'FALSE' || value === false) {
cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.FALSE);
sharedArrayBufferDisabled = true;
} else {
cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.TRUE);
}
await cs3DInit({
rendering: {
preferSizeOverAccuracy: Boolean(appConfig.use16BitDataType),
useNorm16Texture: Boolean(appConfig.use16BitDataType),
},
});
// For debugging e2e tests that are failing on CI
cornerstone.setUseCPURendering(Boolean(appConfig.useCPURendering));
cornerstone.setConfiguration({
...cornerstone.getConfiguration(),
rendering: {
@@ -72,6 +94,7 @@ export default async function init({
cornerstoneViewportService,
hangingProtocolService,
toolGroupService,
toolbarService,
viewportGridService,
stateSyncService,
} = servicesManager.services as CornerstoneServices;
@@ -80,10 +103,15 @@ export default async function init({
window.extensionManager = extensionManager;
window.commandsManager = commandsManager;
if (appConfig.showWarningMessageForCrossOrigin && !window.crossOriginIsolated) {
if (
appConfig.showWarningMessageForCrossOrigin &&
!window.crossOriginIsolated &&
!sharedArrayBufferDisabled
) {
uiNotificationService.show({
title: 'Cross Origin Isolation',
message: 'Cross Origin Isolation is not enabled, volume rendering will not work (e.g., MPR)',
message:
'Cross Origin Isolation is not enabled, read more about it here: https://docs.ohif.org/faq/',
type: 'warning',
});
}
@@ -182,9 +210,45 @@ export default async function init({
commandsManager,
});
const newStackCallback = evt => {
/**
* When a viewport gets a new display set, this call will go through all the
* active tools in the toolbar, and call any commands registered in the
* toolbar service with a callback to re-enable on displaying the viewport.
*/
const toolbarEventListener = evt => {
const { element } = evt.detail;
utilities.stackPrefetch.enable(element);
const activeTools = toolbarService.getActiveTools();
activeTools.forEach(tool => {
const toolData = toolbarService.getNestedButton(tool);
const commands = toolData?.listeners?.[evt.type];
commandsManager.run(commands, { element, evt });
});
};
/** Listens for active viewport events and fires the toolbar listeners */
const activeViewportEventListener = evt => {
const { viewportId } = evt;
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
const activeTools = toolbarService.getActiveTools();
activeTools.forEach(tool => {
if (!toolGroup?._toolInstances?.[tool]) {
return;
}
// check if tool is active on the new viewport
const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled;
if (!toolEnabled) {
return;
}
const button = toolbarService.getNestedButton(tool);
const commands = button?.listeners?.[evt.type];
commandsManager.run(commands, { viewportId, evt });
});
};
const resetCrosshairs = evt => {
@@ -211,11 +275,18 @@ export default async function init({
}
};
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => {
const { element } = evt.detail;
cornerstoneTools.utilities.stackContextPrefetch.enable(element);
});
function elementEnabledHandler(evt) {
const { element } = evt.detail;
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, newStackCallback);
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
initViewTiming({ element, eventTarget });
}
function elementDisabledHandler(evt) {
@@ -236,35 +307,9 @@ export default async function init({
viewportGridService.subscribe(
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
({ viewportId }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {
return;
}
// check if reference lines are active
const referenceLinesEnabled =
toolGroup._toolInstances['ReferenceLines'].mode === Enums.ToolModes.Enabled;
if (!referenceLinesEnabled) {
return;
}
toolGroup.setToolConfiguration(
ReferenceLinesTool.toolName,
{
sourceViewportId: viewportId,
},
true // overwrite
activeViewportEventListener
);
// make sure to set it to enabled again since we want to recalculate
// the source-target lines
toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
}
);
}
function CPUModal() {
return (
+14 -1
View File
@@ -18,6 +18,7 @@ const DEFAULT_CONTEXT_MENU_CLICKS = {
{
commandName: 'showCornerstoneContextMenu',
commandOptions: {
requireNearbyToolData: true,
menuId: 'measurementsContextMenu',
},
},
@@ -62,8 +63,20 @@ function initContextMenu({
const customizations =
customizationService.get('cornerstoneViewportClickCommands') || DEFAULT_CONTEXT_MENU_CLICKS;
const toRun = customizations[name];
if (!toRun) {
return;
}
// only find nearbyToolData if required, for the click (which closes the context menu
// we don't need to find nearbyToolData)
let nearbyToolData = null;
if (toRun.commands.some(command => command.commandOptions?.requireNearbyToolData)) {
nearbyToolData = findNearbyToolData(commandsManager, evt);
}
const options = {
nearbyToolData: findNearbyToolData(commandsManager, evt),
nearbyToolData,
event: evt,
};
commandsManager.run(toRun, options);
@@ -25,6 +25,9 @@ import {
annotation,
ReferenceLinesTool,
TrackballRotateTool,
CircleScissorsTool,
RectangleScissorsTool,
SphereScissorsTool,
} from '@cornerstonejs/tools';
import CalibrationLineTool from './tools/CalibrationLineTool';
@@ -59,6 +62,9 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(ReferenceLinesTool);
addTool(CalibrationLineTool);
addTool(TrackballRotateTool);
addTool(CircleScissorsTool);
addTool(RectangleScissorsTool);
addTool(SphereScissorsTool);
addTool(ImageOverlayViewerTool);
// Modify annotation tools to use dashed lines on SR
@@ -101,6 +107,9 @@ const toolNames = {
ReferenceLines: ReferenceLinesTool.toolName,
CalibrationLine: CalibrationLineTool.toolName,
TrackballRotateTool: TrackballRotateTool.toolName,
CircleScissors: CircleScissorsTool.toolName,
RectangleScissors: RectangleScissorsTool.toolName,
SphereScissors: SphereScissorsTool.toolName,
ImageOverlayViewer: ImageOverlayViewerTool.toolName,
};
@@ -49,6 +49,7 @@ export default function initWADOImageLoader(
// Until the default is set to true (which is the case for cornerstone3D),
// we should set this flag to false.
convertFloatPixelDataToInt: false,
use16BitDataType: Boolean(appConfig.use16BitDataType),
},
beforeSend: function (xhr) {
//TODO should be removed in the future and request emitted by DicomWebDataSource
@@ -210,7 +210,7 @@ class CornerstoneCacheService {
}
private _shouldRenderSegmentation(displaySets) {
const { segmentationService } = this.servicesManager.services;
const { segmentationService, displaySetService } = this.servicesManager.services;
const viewportDisplaySetInstanceUIDs = displaySets.map(
({ displaySetInstanceUID }) => displaySetInstanceUID
@@ -222,10 +222,13 @@ class CornerstoneCacheService {
for (const segmentation of segmentations) {
const segDisplaySetInstanceUID = segmentation.displaySetInstanceUID;
const segDisplaySet = displaySetService.getDisplaySetByUID(segDisplaySetInstanceUID);
const instance = segDisplaySet.instances?.[0] || segDisplaySet.instance;
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
viewportDisplaySetInstanceUIDs,
segDisplaySetInstanceUID
instance.FrameOfReferenceUID
);
if (shouldDisplaySeg) {
@@ -7,7 +7,6 @@ import {
geometryLoader,
eventTarget,
getEnabledElementByIds,
metaData,
utilities as csUtils,
volumeLoader,
} from '@cornerstonejs/core';
@@ -41,7 +40,7 @@ const EVENTS = {
SEGMENTATION_CONFIGURATION_CHANGED: 'event::segmentation_configuration_changed',
// fired when the active segment is loaded in SEG or RTSTRUCT
SEGMENT_LOADING_COMPLETE: 'event::segment_loading_complete',
// for all segments
// loading completed for all segments
SEGMENTATION_LOADING_COMPLETE: 'event::segmentation_loading_complete',
};
@@ -99,43 +98,51 @@ class SegmentationService extends PubSubService {
};
/**
* It adds a segment to a segmentation, basically just setting the properties for
* the segment.
* @param segmentationId - The ID of the segmentation you want to add a
* segment to.
* @param segmentIndex - The index of the segment to add.
* @param properties - The properties of the segment to add including
* -- label: the label of the segment
* -- color: the color of the segment
* -- opacity: the opacity of the segment
* -- visibility: the visibility of the segment (boolean)
* -- isLocked: whether the segment is locked for editing
* -- active: whether the segment is currently the active segment to be edited
* Adds a new segment to the specified segmentation.
* @param segmentationId - The ID of the segmentation to add the segment to.
* @param config - An object containing the configuration options for the new segment.
* - segmentIndex: (optional) The index of the segment to add. If not provided, the next available index will be used.
* - toolGroupId: (optional) The ID of the tool group to associate the new segment with. If not provided, the first available tool group will be used.
* - properties: (optional) An object containing the properties of the new segment.
* - label: (optional) The label of the new segment. If not provided, a default label will be used.
* - color: (optional) The color of the new segment in RGB format. If not provided, a default color will be used.
* - opacity: (optional) The opacity of the new segment. If not provided, a default opacity will be used.
* - visibility: (optional) Whether the new segment should be visible. If not provided, the segment will be visible by default.
* - isLocked: (optional) Whether the new segment should be locked for editing. If not provided, the segment will not be locked by default.
* - active: (optional) Whether the new segment should be the active segment to be edited. If not provided, the segment will not be active by default.
*/
public addSegment(
segmentationId: string,
segmentIndex: number,
toolGroupId?: string,
properties?: {
label?: string;
color?: ohifTypes.RGB;
opacity?: number;
visibility?: boolean;
isLocked?: boolean;
active?: boolean;
}
config: {
segmentIndex?: number;
toolGroupId?: string;
properties?: {
label?: string;
color?: ohifTypes.RGB;
opacity?: number;
visibility?: boolean;
isLocked?: boolean;
active?: boolean;
};
} = {}
): void {
if (segmentIndex === 0) {
if (config?.segmentIndex === 0) {
throw new Error('Segment index 0 is reserved for "no label"');
}
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
const toolGroupId = config.toolGroupId ?? this._getApplicableToolGroupId();
const { segmentationRepresentationUID, segmentation } = this._getSegmentationInfo(
segmentationId,
toolGroupId
);
let segmentIndex = config.segmentIndex;
if (!segmentIndex) {
// grab the next available segment index
segmentIndex = segmentation.segments.length === 0 ? 1 : segmentation.segments.length;
}
if (this._getSegmentInfo(segmentation, segmentIndex)) {
throw new Error(`Segment ${segmentIndex} already exists`);
}
@@ -147,7 +154,7 @@ class SegmentationService extends PubSubService {
);
segmentation.segments[segmentIndex] = {
label: properties.label,
label: config.properties?.label ?? `Segment ${segmentIndex}`,
segmentIndex: segmentIndex,
color: [rgbaColor[0], rgbaColor[1], rgbaColor[2]],
opacity: rgbaColor[3],
@@ -157,9 +164,12 @@ class SegmentationService extends PubSubService {
segmentation.segmentCount++;
// make the newly added segment the active segment
this._setActiveSegment(segmentationId, segmentIndex);
const suppressEvents = true;
if (properties !== undefined) {
const { color: newColor, opacity, isLocked, visibility, active } = properties;
if (config.properties !== undefined) {
const { color: newColor, opacity, isLocked, visibility, active } = config.properties;
if (newColor !== undefined) {
this._setSegmentColor(segmentationId, segmentIndex, newColor, toolGroupId, suppressEvents);
@@ -179,7 +189,7 @@ class SegmentationService extends PubSubService {
);
}
if (active !== undefined) {
if (active === true) {
this._setActiveSegment(segmentationId, segmentIndex, suppressEvents);
}
@@ -281,17 +291,21 @@ class SegmentationService extends PubSubService {
);
}
public setSegmentLockedForSegmentation(
segmentationId: string,
segmentIndex: number,
isLocked: boolean
): void {
public setSegmentLocked(segmentationId: string, segmentIndex: number, isLocked: boolean): void {
const suppressEvents = false;
this._setSegmentLocked(segmentationId, segmentIndex, isLocked, suppressEvents);
}
public setSegmentLabel(segmentationId: string, segmentIndex: number, segmentLabel: string): void {
this._setSegmentLabel(segmentationId, segmentIndex, segmentLabel);
/**
* Toggles the locked state of a segment in a segmentation.
* @param segmentationId - The ID of the segmentation.
* @param segmentIndex - The index of the segment to toggle.
*/
public toggleSegmentLocked(segmentationId: string, segmentIndex: number): void {
const segmentation = this.getSegmentation(segmentationId);
const segment = this._getSegmentInfo(segmentation, segmentIndex);
const isLocked = !segment.isLocked;
this._setSegmentLocked(segmentationId, segmentIndex, isLocked);
}
public setSegmentColor(
@@ -347,13 +361,13 @@ class SegmentationService extends PubSubService {
}
public setActiveSegmentationForToolGroup(segmentationId: string, toolGroupId?: string): void {
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const suppressEvents = false;
this._setActiveSegmentationForToolGroup(segmentationId, toolGroupId, suppressEvents);
}
public setActiveSegmentForSegmentation(segmentationId: string, segmentIndex: number): void {
public setActiveSegment(segmentationId: string, segmentIndex: number): void {
this._setActiveSegment(segmentationId, segmentIndex, false);
}
@@ -385,6 +399,23 @@ class SegmentationService extends PubSubService {
return segmentations && segmentations.map(m => this.segmentations[Object.keys(m)[0]]);
}
public getActiveSegmentation(): Segmentation {
const segmentations = this.getSegmentations();
return segmentations.find(segmentation => segmentation.isActive);
}
public getActiveSegment() {
const activeSegmentation = this.getActiveSegmentation();
const { activeSegmentIndex, segments } = activeSegmentation;
if (activeSegmentIndex === null) {
return;
}
return segments[activeSegmentIndex];
}
/**
* Get specific segmentation by its id.
*
@@ -434,12 +465,14 @@ class SegmentationService extends PubSubService {
},
]);
// Define a new color LUT and associate it with this segmentation.
// Todo: need to be generalized to accept custom color LUTs
const newColorLUT = this.generateNewColorLUT();
const newColorLUTIndex = this.getNextColorLUTIndex();
cstSegmentation.config.color.addColorLUT(newColorLUT, newColorLUTIndex);
// if first segmentation, we can use the default colorLUT, otherwise
// we need to generate a new one and use a new colorLUT
const colorLUTIndex = 0;
if (Object.keys(this.segmentations).length !== 0) {
const newColorLUT = this.generateNewColorLUT();
const colorLUTIndex = this.getNextColorLUTIndex();
cstSegmentation.config.color.addColorLUT(newColorLUT, colorLUTIndex);
}
this.segmentations[segmentationId] = {
...segmentation,
@@ -448,8 +481,8 @@ class SegmentationService extends PubSubService {
activeSegmentIndex: segmentation.activeSegmentIndex ?? null,
segmentCount: segmentation.segmentCount ?? 0,
isActive: false,
colorLUTIndex: newColorLUTIndex,
isVisible: true,
colorLUTIndex,
};
cachedSegmentation = this.segmentations[segmentationId];
@@ -526,7 +559,7 @@ class SegmentationService extends PubSubService {
volumeId: segmentationId,
targetBuffer: {
type: 'Uint8Array',
sharedArrayBuffer: true,
sharedArrayBuffer: window.SharedArrayBuffer,
},
});
const derivedVolumeScalarData = derivedVolume.getScalarData();
@@ -736,6 +769,97 @@ class SegmentationService extends PubSubService {
return this.addOrUpdateSegmentation(segmentation, suppressEvents);
}
// Todo: this should not run on the main thread
public calculateCentroids = (
segmentationId: string,
segmentIndex?: number
): Map<number, { x: number; y: number; z: number; world: number[] }> => {
const segmentation = this.getSegmentation(segmentationId);
const volume = this.getLabelmapVolume(segmentationId);
const { dimensions, imageData } = volume;
const scalarData = volume.getScalarData();
const [dimX, dimY, numFrames] = dimensions;
const frameLength = dimX * dimY;
const segmentIndices = segmentIndex
? [segmentIndex]
: segmentation.segments
.filter(segment => segment?.segmentIndex)
.map(segment => segment.segmentIndex);
const segmentIndicesSet = new Set(segmentIndices);
const centroids = new Map();
for (const index of segmentIndicesSet) {
centroids.set(index, { x: 0, y: 0, z: 0, count: 0 });
}
let voxelIndex = 0;
for (let frame = 0; frame < numFrames; frame++) {
for (let p = 0; p < frameLength; p++) {
const segmentIndex = scalarData[voxelIndex++];
if (segmentIndicesSet.has(segmentIndex)) {
const centroid = centroids.get(segmentIndex);
centroid.x += p % dimX;
centroid.y += (p / dimX) | 0;
centroid.z += frame;
centroid.count++;
}
}
}
const result = new Map();
for (const [index, centroid] of centroids) {
const count = centroid.count;
const normalizedCentroid = {
x: centroid.x / count,
y: centroid.y / count,
z: centroid.z / count,
};
normalizedCentroid.world = imageData.indexToWorld([
normalizedCentroid.x,
normalizedCentroid.y,
normalizedCentroid.z,
]);
result.set(index, normalizedCentroid);
}
this.setCentroids(segmentationId, result);
return result;
};
private setCentroids = (
segmentationId: string,
centroids: Map<number, { image: number[]; world?: number[] }>
): void => {
const segmentation = this.getSegmentation(segmentationId);
const imageData = this.getLabelmapVolume(segmentationId).imageData; // Assuming this method returns imageData
if (!segmentation.cachedStats) {
segmentation.cachedStats = { segmentCenter: {} };
} else if (!segmentation.cachedStats.segmentCenter) {
segmentation.cachedStats.segmentCenter = {};
}
for (const [segmentIndex, centroid] of centroids) {
let world = centroid.world;
// If world coordinates are not provided, calculate them
if (!world || world.length === 0) {
world = imageData.indexToWorld(centroid.image);
}
segmentation.cachedStats.segmentCenter[segmentIndex] = {
center: {
image: centroid.image,
world: world,
},
};
}
this.addOrUpdateSegmentation(segmentation, true, true);
};
public jumpToSegmentCenter(
segmentationId: string,
segmentIndex: number,
@@ -749,6 +873,10 @@ class SegmentationService extends PubSubService {
const { toolGroupService } = this.servicesManager.services;
const center = this._getSegmentCenter(segmentationId, segmentIndex);
if (!center?.world) {
return;
}
const { world } = center;
// todo: generalize
@@ -801,7 +929,7 @@ class SegmentationService extends PubSubService {
}
const segmentation = this.getSegmentation(segmentationId);
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const segmentationRepresentation = this._getSegmentationRepresentation(
segmentationId,
@@ -831,6 +959,7 @@ class SegmentationService extends PubSubService {
displaySetInstanceUID: string,
options?: {
segmentationId: string;
FrameOfReferenceUID: string;
label: string;
}
): Promise<string> => {
@@ -851,7 +980,7 @@ class SegmentationService extends PubSubService {
volumeId: segmentationId,
targetBuffer: {
type: 'Uint8Array',
sharedArrayBuffer: true,
sharedArrayBuffer: window.SharedArrayBuffer,
},
});
@@ -865,6 +994,8 @@ class SegmentationService extends PubSubService {
// We should set it as active by default, as it created for display
isActive: true,
type: representationType,
FrameOfReferenceUID:
options?.FrameOfReferenceUID || displaySet.instances?.[0]?.FrameOfReferenceUID,
representationData: {
LABELMAP: {
volumeId: segmentationId,
@@ -892,7 +1023,8 @@ class SegmentationService extends PubSubService {
toolGroupId: string,
segmentationId: string,
hydrateSegmentation = false,
representationType = csToolsEnums.SegmentationRepresentations.Labelmap
representationType = csToolsEnums.SegmentationRepresentations.Labelmap,
suppressEvents = false
): Promise<void> => {
const segmentation = this.getSegmentation(segmentationId);
@@ -959,13 +1091,19 @@ class SegmentationService extends PubSubService {
);
}
if (isLocked !== undefined) {
if (isLocked) {
this._setSegmentLocked(segmentationId, segmentIndex, isLocked, suppressEvents);
}
}
if (!suppressEvents) {
this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, {
segmentation,
});
}
};
public setSegmentRGBAColorForSegmentation = (
public setSegmentRGBAColor = (
segmentationId: string,
segmentIndex: number,
rgbaColor,
@@ -1008,11 +1146,11 @@ class SegmentationService extends PubSubService {
if (!segmentation) {
throw new Error(`Segmentation with segmentationId ${segmentationId} not found.`);
}
this._setDisplaySetIsHydrated(segmentationId, true);
segmentation.hydrated = true;
// Not all segmentations have dipslaysets, some of them are derived in the client
this._setDisplaySetIsHydrated(segmentationId, true);
if (!suppressEvents) {
this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, {
segmentation,
@@ -1021,8 +1159,13 @@ class SegmentationService extends PubSubService {
};
private _setDisplaySetIsHydrated(displaySetUID: string, isHydrated: boolean): void {
const { DisplaySetService: displaySetService } = this.servicesManager.services;
const { displaySetService } = this.servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID(displaySetUID);
if (!displaySet) {
return;
}
displaySet.isHydrated = isHydrated;
displaySetService.setDisplaySetMetadataInvalidated(displaySetUID, false);
}
@@ -1167,7 +1310,6 @@ class SegmentationService extends PubSubService {
}
const { colorLUTIndex } = segmentation;
this._removeSegmentationFromCornerstone(segmentationId);
// Delete associated colormap
@@ -1181,10 +1323,14 @@ class SegmentationService extends PubSubService {
if (wasActive) {
const remainingSegmentations = this._getSegmentations();
if (remainingSegmentations.length) {
const { id } = remainingSegmentations[0];
const remainingHydratedSegmentations = remainingSegmentations.filter(
segmentation => segmentation.hydrated
);
this._setActiveSegmentationForToolGroup(id, this._getFirstToolGroupId(), false);
if (remainingHydratedSegmentations.length) {
const { id } = remainingHydratedSegmentations[0];
this._setActiveSegmentationForToolGroup(id, this._getApplicableToolGroupId(), false);
}
}
@@ -1196,7 +1342,7 @@ class SegmentationService extends PubSubService {
}
public getConfiguration = (toolGroupId?: string): SegmentationConfig => {
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const brushSize = 1;
// const brushSize = cstUtils.segmentation.getBrushSizeForToolGroup(
@@ -1312,15 +1458,11 @@ class SegmentationService extends PubSubService {
return cstSegmentation.state.getSegmentationRepresentations(toolGroupId);
};
public setSegmentLabelForSegmentation(
segmentationId: string,
segmentIndex: number,
label: string
) {
this._setSegmentLabelForSegmentation(segmentationId, segmentIndex, label);
public setSegmentLabel(segmentationId: string, segmentIndex: number, label: string) {
this._setSegmentLabel(segmentationId, segmentIndex, label);
}
private _setSegmentLabelForSegmentation(
private _setSegmentLabel(
segmentationId: string,
segmentIndex: number,
label: string,
@@ -1348,8 +1490,8 @@ class SegmentationService extends PubSubService {
}
}
public shouldRenderSegmentation(viewportDisplaySetInstanceUIDs, segDisplaySetInstanceUID) {
if (!viewportDisplaySetInstanceUIDs || !viewportDisplaySetInstanceUIDs.length) {
public shouldRenderSegmentation(viewportDisplaySetInstanceUIDs, segmentationFrameOfReferenceUID) {
if (!viewportDisplaySetInstanceUIDs?.length) {
return false;
}
@@ -1357,10 +1499,6 @@ class SegmentationService extends PubSubService {
let shouldDisplaySeg = false;
const segDisplaySet = displaySetService.getDisplaySetByUID(segDisplaySetInstanceUID);
const segFrameOfReferenceUID = this._getFrameOfReferenceUIDForSeg(segDisplaySet);
// check if the displaySet is sharing the same frameOfReferenceUID
// with the new segmentation
for (const displaySetInstanceUID of viewportDisplaySetInstanceUIDs) {
@@ -1370,7 +1508,7 @@ class SegmentationService extends PubSubService {
// don't want to show the segmentation for all the frames
if (
displaySet.isReconstructable &&
displaySet?.images?.[0]?.FrameOfReferenceUID === segFrameOfReferenceUID
displaySet?.images?.[0]?.FrameOfReferenceUID === segmentationFrameOfReferenceUID
) {
shouldDisplaySeg = true;
break;
@@ -1499,7 +1637,7 @@ class SegmentationService extends PubSubService {
throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`);
}
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const segmentationRepresentation = this._getSegmentationRepresentation(
segmentationId,
@@ -1593,7 +1731,7 @@ class SegmentationService extends PubSubService {
toolGroupId?: string,
suppressEvents = false
) {
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const { segmentationRepresentationUID, segmentation } = this._getSegmentationInfo(
segmentationId,
@@ -1652,7 +1790,7 @@ class SegmentationService extends PubSubService {
throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`);
}
toolGroupId = toolGroupId ?? this._getFirstToolGroupId();
toolGroupId = toolGroupId ?? this._getApplicableToolGroupId();
const segmentationRepresentation = this._getSegmentationRepresentation(
segmentationId,
@@ -1717,7 +1855,7 @@ class SegmentationService extends PubSubService {
const segmentationRepresentations =
this.getSegmentationRepresentationsForToolGroup(toolGroupId);
if (segmentationRepresentations.length === 0) {
if (!segmentationRepresentations?.length) {
return;
}
@@ -1969,11 +2107,21 @@ class SegmentationService extends PubSubService {
}
}
private _getFirstToolGroupId = () => {
const { toolGroupService } = this.servicesManager.services;
const toolGroupIds = toolGroupService.getToolGroupIds();
private _getApplicableToolGroupId = () => {
const { toolGroupService, viewportGridService, cornerstoneViewportService } =
this.servicesManager.services;
return toolGroupIds[0];
const viewportInfo = cornerstoneViewportService.getViewportInfo(
viewportGridService.getActiveViewportId()
);
if (!viewportInfo) {
const toolGroupIds = toolGroupService.getToolGroupIds();
return toolGroupIds[0];
}
return viewportInfo.getToolGroupId();
};
private getNextColorLUTIndex = (): number => {
@@ -39,6 +39,8 @@ type Segmentation = {
isActive: boolean;
// if the segmentation is visible in the viewer
isVisible: boolean;
// the frame of reference UID of the segmentation
FrameOfReferenceUID: string;
// the label of the segmentation
label: string;
// the number of segments in the segmentation
@@ -97,9 +97,9 @@ export default class ToolGroupService {
}
public getActiveToolForViewport(viewportId: string): string {
const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId);
const toolGroup = this.getToolGroupForViewport(viewportId);
if (!toolGroup) {
return null;
return;
}
return toolGroup.getActivePrimaryMouseButtonTool();
@@ -184,13 +184,9 @@ export default class ToolGroupService {
this._setToolsMode(toolGroup, tools);
}
public createToolGroupAndAddTools(
toolGroupId: string,
tools: Array<Tool>,
configs: any = {}
): Types.IToolGroup {
public createToolGroupAndAddTools(toolGroupId: string, tools: Array<Tool>): Types.IToolGroup {
const toolGroup = this.createToolGroup(toolGroupId);
this.addToolsToToolGroup(toolGroupId, tools, configs);
this.addToolsToToolGroup(toolGroupId, tools);
return toolGroup;
}
@@ -239,34 +235,6 @@ export default class ToolGroupService {
toolInstance.configuration = config;
}
private _getToolNames(toolGroupTools: Tools): string[] {
const toolNames = [];
if (toolGroupTools.active) {
toolGroupTools.active.forEach(tool => {
toolNames.push(tool.toolName);
});
}
if (toolGroupTools.passive) {
toolGroupTools.passive.forEach(tool => {
toolNames.push(tool.toolName);
});
}
if (toolGroupTools.enabled) {
toolGroupTools.enabled.forEach(tool => {
toolNames.push(tool.toolName);
});
}
if (toolGroupTools.disabled) {
toolGroupTools.disabled.forEach(tool => {
toolNames.push(tool.toolName);
});
}
return toolNames;
}
private _setToolsMode(toolGroup, tools) {
const { active, passive, enabled, disabled } = tools;
@@ -295,17 +263,33 @@ export default class ToolGroupService {
}
}
private _addTools(toolGroup, tools, configs) {
const toolNames = this._getToolNames(tools);
toolNames.forEach(toolName => {
// Initialize the toolConfig if no configuration is provided
const toolConfig = configs[toolName] ?? {};
private _addTools(toolGroup, tools) {
const addTools = tools => {
tools.forEach(({ toolName, parentTool, configuration }) => {
if (parentTool) {
toolGroup.addToolInstance(toolName, parentTool, {
...configuration,
});
} else {
toolGroup.addTool(toolName, { ...configuration });
}
});
};
// if (volumeUID) {
// toolConfig.volumeUID = volumeUID;
// }
if (tools.active) {
addTools(tools.active);
}
toolGroup.addTool(toolName, { ...toolConfig });
});
if (tools.passive) {
addTools(tools.passive);
}
if (tools.enabled) {
addTools(tools.enabled);
}
if (tools.disabled) {
addTools(tools.disabled);
}
}
}
@@ -15,11 +15,7 @@ import {
import { utilities as csToolsUtils, Enums as csToolsEnums } from '@cornerstonejs/tools';
import { IViewportService } from './IViewportService';
import { RENDERING_ENGINE_ID } from './constants';
import ViewportInfo, {
ViewportOptions,
DisplaySetOptions,
PublicViewportOptions,
} from './Viewport';
import ViewportInfo, { DisplaySetOptions, PublicViewportOptions } from './Viewport';
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
import { Presentation, Presentations } from '../../types/Presentation';
@@ -253,6 +249,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const type = viewportInfo.getViewportType();
const background = viewportInfo.getBackground();
const orientation = viewportInfo.getOrientation();
const displayArea = viewportInfo.getDisplayArea();
const viewportInput: Types.PublicViewportInput = {
viewportId,
@@ -261,6 +258,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
defaultOptions: {
background,
orientation,
displayArea,
},
};
@@ -586,10 +584,23 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
continue;
}
// otherwise, check if the hydrated segmentations are in the same FOR
// otherwise, check if the hydrated segmentations are in the same FrameOfReferenceUID
// as the primary displaySet, if so add the representation (since it was not there)
const { id: segDisplaySetInstanceUID, type } = segmentation;
const segFrameOfReferenceUID = this._getFrameOfReferenceUID(segDisplaySetInstanceUID);
const { id: segDisplaySetInstanceUID } = segmentation;
let segFrameOfReferenceUID = this._getFrameOfReferenceUID(segDisplaySetInstanceUID);
if (!segFrameOfReferenceUID) {
// if the segmentation displaySet does not have a FrameOfReferenceUID, we might check the
// segmentation itself maybe it has a FrameOfReferenceUID
const { FrameOfReferenceUID } = segmentation;
if (FrameOfReferenceUID) {
segFrameOfReferenceUID = FrameOfReferenceUID;
}
}
if (!segFrameOfReferenceUID) {
return;
}
let shouldDisplaySeg = false;
@@ -21,6 +21,7 @@ export type ViewportOptions = {
presentationIds?: UITypes.PresentationIds;
orientation?: Enums.OrientationAxis;
background?: Types.Point3;
displayArea?: Types.DisplayArea;
syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions;
customViewportProps?: Record<string, unknown>;
@@ -39,6 +40,7 @@ export type PublicViewportOptions = {
viewportId?: string;
orientation?: Enums.OrientationAxis;
background?: Types.Point3;
displayArea?: Types.DisplayArea;
syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions;
customViewportProps?: Record<string, unknown>;
@@ -265,6 +267,10 @@ class ViewportInfo {
return this.viewportOptions.orientation;
}
public getDisplayArea(): Types.DisplayArea {
return this.viewportOptions.displayArea;
}
public getInitialImageOptions(): InitialImageOptions {
return this.viewportOptions.initialImageOptions;
}
@@ -66,14 +66,6 @@ export function onCompletedCalibrationLine(servicesManager, csToolsEvent) {
calculateLength3(annotationData.handles.points[0], annotationData.handles.points[1]) * 100
) / 100;
// calculate the currently applied pixel spacing on the viewport
const calibratedPixelSpacing = metaData.get('calibratedPixelSpacing', imageId);
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
const currentRowPixelSpacing =
calibratedPixelSpacing?.[0] || imagePlaneModule?.rowPixelSpacing || 1;
const currentColumnPixelSpacing =
calibratedPixelSpacing?.[1] || imagePlaneModule?.columnPixelSpacing || 1;
const adjustCalibration = newLength => {
const spacingScale = newLength / length;
@@ -1,5 +1,6 @@
import { metaData } from '@cornerstonejs/core';
import { VolumeViewport, metaData } from '@cornerstonejs/core';
import { utilities } from '@cornerstonejs/core';
import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types';
import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools';
import { guid } from '@ohif/core/src/utils';
@@ -46,6 +47,15 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
this._cachedOverlayMetadata = new Map();
};
protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string {
if (viewport instanceof VolumeViewport) {
return;
}
const targetId = this.getTargetId(viewport);
return targetId.split('imageId:')[1];
}
renderAnnotation = (enabledElement, svgDrawingHelper) => {
const { viewport } = enabledElement;
@@ -150,6 +160,8 @@ class ImageOverlayViewerTool extends AnnotationDisplayTool {
let pixelData = null;
if (overlay.pixelData.Value) {
pixelData = overlay.pixelData.Value;
} else if (overlay.pixelData instanceof Array) {
pixelData = overlay.pixelData[0];
} else if (overlay.pixelData.retrieveBulkData) {
pixelData = await overlay.pixelData.retrieveBulkData();
}
@@ -142,12 +142,16 @@ const CornerstoneViewportDownloadForm = ({
const properties = viewport.getProperties();
downloadViewport.setStack([imageId]).then(() => {
downloadViewport.setProperties(properties);
try {
downloadViewport.setProperties(properties);
const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);
const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);
resolve({ width: newWidth, height: newHeight });
resolve({ width: newWidth, height: newHeight });
} catch (e) {
// Happens on clicking the cancel button
console.warn('Unable to set properties', e);
}
});
} else if (downloadViewport instanceof VolumeViewport) {
const actors = viewport.getActors();
@@ -97,7 +97,7 @@ class DicomLoaderService {
if (
(!imageInstance && !nonImageInstance) ||
!nonImageInstance.imageId.startsWith('dicomfile')
!nonImageInstance.imageId?.startsWith('dicomfile')
) {
return;
}
@@ -0,0 +1,45 @@
import { log, Types } from '@ohif/core';
import { EVENTS } from '@cornerstonejs/core';
const { TimingEnum } = Types;
const IMAGE_TIMING_KEYS = [
TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES,
TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE,
TimingEnum.STUDY_TO_FIRST_IMAGE,
];
const imageTiming = {
viewportsWaiting: 0,
};
/**
* Defines the initial view timing reporting.
* This allows knowing how many viewports are waiting for initial views and
* when the IMAGE_RENDERED gets sent out.
* The first image rendered will fire the FIRST_IMAGE timeEnd logs, while
* the last of the enabled viewport will fire the ALL_IMAGES timeEnd logs.
*
*/
export default function initViewTiming({ element }) {
if (!IMAGE_TIMING_KEYS.find(key => log.timingKeys[key])) {
return;
}
imageTiming.viewportsWaiting += 1;
element.addEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener);
}
function imageRenderedListener(evt) {
if (evt.detail.viewportStatus === 'preRender') {
return;
}
log.timeEnd(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
log.timeEnd(TimingEnum.STUDY_TO_FIRST_IMAGE);
log.timeEnd(TimingEnum.SCRIPT_TO_VIEW);
imageTiming.viewportsWaiting -= 1;
evt.detail.element.removeEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener);
if (!imageTiming.viewportsWaiting) {
log.timeEnd(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
}
}
@@ -1,4 +1,5 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
@@ -181,7 +182,9 @@ function getDisplayText(mappedAnnotations, displaySet) {
return displayText;
}
const roundedAngle = utils.roundNumber(angle, 2);
displayText.push(`${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`);
displayText.push(
`${roundedAngle} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})`
);
return displayText;
}
@@ -3,6 +3,7 @@ import { annotation } from '@cornerstonejs/tools';
import SUPPORTED_TOOLS from './constants/supportedTools';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
import { getDisplayUnit } from './utils';
const Bidirectional = {
toAnnotation: measurement => {},
@@ -161,14 +162,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
const displayText = [];
// Area is the same for all series
const {
length,
width,
unit,
SeriesNumber,
SOPInstanceUID,
frameNumber,
} = mappedAnnotations[0];
const { length, width, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
const roundedLength = utils.roundNumber(length, 2);
const roundedWidth = utils.roundNumber(width, 2);
@@ -183,9 +177,9 @@ function getDisplayText(mappedAnnotations, displaySet) {
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
displayText.push(
`L: ${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`
`L: ${roundedLength} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})`
);
displayText.push(`W: ${roundedWidth} ${unit}`);
displayText.push(`W: ${roundedWidth} ${getDisplayUnit(unit)}`);
return displayText;
}
@@ -1,4 +1,5 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
@@ -138,13 +139,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
return;
}
columns.push(
`max (${unit})`,
`mean (${unit})`,
`std (${unit})`,
'Area',
'Unit'
);
columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, 'Area', 'Unit');
values.push(max, mean, stdDev, area, areaUnit);
});
@@ -189,7 +184,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
// Area sometimes becomes undefined if `preventHandleOutsideImage` is off.
const roundedArea = utils.roundNumber(area || 0, 2);
displayText.push(`${roundedArea} ${areaUnit}`);
displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
@@ -198,7 +193,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
let maxStr = '';
if (max) {
const roundedMax = utils.roundNumber(max, 2);
maxStr = `Max: ${roundedMax} <small>${unit}</small> `;
maxStr = `Max: ${roundedMax} <small>${getDisplayUnit(unit)}</small> `;
}
const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
@@ -1,4 +1,5 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
@@ -181,7 +182,9 @@ function getDisplayText(mappedAnnotations, displaySet) {
return displayText;
}
const roundedAngle = utils.roundNumber(angle, 2);
displayText.push(`${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})`);
displayText.push(
`${roundedAngle} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})`
);
return displayText;
}
@@ -1,4 +1,5 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
@@ -138,13 +139,7 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
return;
}
columns.push(
`max (${unit})`,
`mean (${unit})`,
`std (${unit})`,
'Area',
'Unit'
);
columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, 'Area', 'Unit');
values.push(max, mean, stdDev, area, areaUnit);
});
@@ -188,7 +183,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
const roundedArea = utils.roundNumber(area, 2);
displayText.push(`${roundedArea} ${areaUnit}`);
displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
@@ -197,7 +192,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
let maxStr = '';
if (max) {
const roundedMax = utils.roundNumber(max, 2);
maxStr = `Max: ${roundedMax} <small>${unit}</small> `;
maxStr = `Max: ${roundedMax} <small>${getDisplayUnit(unit)}</small> `;
}
const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
@@ -1,4 +1,5 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
@@ -25,11 +26,7 @@ const RectangleROI = {
throw new Error('Tool not supported');
}
const {
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = getSOPInstanceAttributes(
const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
referencedImageId,
CornerstoneViewportService,
viewportId
@@ -92,11 +89,8 @@ function getMappedAnnotations(annotation, DisplaySetService) {
throw new Error('Non-acquisition plane measurement mapping not supported');
}
const {
SOPInstanceUID,
SeriesInstanceUID,
frameNumber,
} = getSOPInstanceAttributes(referencedImageId);
const { SOPInstanceUID, SeriesInstanceUID, frameNumber } =
getSOPInstanceAttributes(referencedImageId);
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
@@ -190,7 +184,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
// Area sometimes becomes undefined if `preventHandleOutsideImage` is off.
const roundedArea = utils.roundNumber(area || 0, 2);
displayText.push(`${roundedArea} ${areaUnit}`);
displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`);
// Todo: we need a better UI for displaying all these information
mappedAnnotations.forEach(mappedAnnotation => {
@@ -199,7 +193,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
let maxStr = '';
if (max) {
const roundedMax = utils.roundNumber(max, 2);
maxStr = `Max: ${roundedMax} <small>${unit}</small> `;
maxStr = `Max: ${roundedMax} <small>${getDisplayUnit(unit)}</small> `;
}
const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
@@ -0,0 +1,3 @@
const getDisplayUnit = unit => (unit == null ? '' : unit);
export default getDisplayUnit;
@@ -5,6 +5,7 @@ import {
getFirstAnnotationSelected,
} from './selection';
import getSOPInstanceAttributes from './getSOPInstanceAttributes';
import getDisplayUnit from './getDisplayUnit';
export {
getHandlesFromPoints,
@@ -12,4 +13,5 @@ export {
isAnnotationSelected,
setAnnotationSelected,
getFirstAnnotationSelected,
getDisplayUnit,
};
@@ -1,90 +1,81 @@
import calculateViewportRegistrations from './calculateViewportRegistrations';
const STACK_SYNC_NAME = 'stackImageSync';
// [ {
// synchronizerId: string,
// viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...]
// ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = [];
export default function toggleStackImageSync({
toggledState,
servicesManager,
viewports: providedViewports,
}) {
if (!toggledState) {
return disableSync(STACK_SYNC_NAME, servicesManager);
}
export default function toggleStackImageSync({ toggledState, servicesManager, getEnabledElement }) {
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
servicesManager.services;
if (!toggledState) {
STACK_IMAGE_SYNC_GROUPS_INFO.forEach(syncGroupInfo => {
const { viewports, synchronizerId } = syncGroupInfo;
const viewports =
providedViewports || getReconstructableStackViewports(viewportGridService, displaySetService);
viewports.forEach(({ viewportId, renderingEngineId }) => {
syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, synchronizerId);
});
});
return;
}
STACK_IMAGE_SYNC_GROUPS_INFO = [];
// create synchronization groups and add viewports
const { viewports } = viewportGridService.getState();
// filter empty viewports
const viewportsArray = Array.from(viewports.values())
.filter(viewport => viewport.displaySetInstanceUIDs?.length)
// filter reconstructable viewports
.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
return !!displaySet?.isReconstructable;
}
});
const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => {
const { viewportId, viewportType } = viewport.viewportOptions;
if (viewportType !== 'stack') {
console.warn('Viewport is not a stack, cannot sync images yet');
return acc;
// create synchronization group and add the viewports to it.
viewports.forEach(gridViewport => {
const { viewportId } = gridViewport.viewportOptions;
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
const { element } = cornerstoneViewportService.getViewportInfo(viewportId);
const { viewport: csViewport, renderingEngineId } = getEnabledElement(element);
const { viewPlaneNormal } = csViewport.getCamera();
// Should we round here? I guess so, but not sure how much precision we need
const orientation = viewPlaneNormal.map(v => Math.round(v)).join(',');
if (!acc[orientation]) {
acc[orientation] = [];
}
acc[orientation].push({ viewportId, renderingEngineId });
return acc;
}, {});
// create synchronizer for each group
Object.values(viewportsByOrientation).map(viewports => {
let synchronizerId = viewports.map(({ viewportId }) => viewportId).join(',');
synchronizerId = `imageSync_${synchronizerId}`;
calculateViewportRegistrations(viewports);
viewports.forEach(({ viewportId, renderingEngineId }) => {
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, {
type: 'stackimage',
id: synchronizerId,
source: true,
target: true,
});
});
STACK_IMAGE_SYNC_GROUPS_INFO.push({
synchronizerId,
viewports,
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
type: 'stackimage',
id: STACK_SYNC_NAME,
source: true,
target: true,
});
});
}
function disableSync(syncName, servicesManager) {
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
servicesManager.services;
const viewports = getReconstructableStackViewports(viewportGridService, displaySetService);
viewports.forEach(gridViewport => {
const { viewportId } = gridViewport.viewportOptions;
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
syncGroupService.removeViewportFromSyncGroup(
viewport.id,
viewport.getRenderingEngine().id,
syncName
);
});
}
/**
* Gets the consistent spacing stack viewport types, which are the ones which
* can be navigated using the stack image sync right now.
*/
function getReconstructableStackViewports(viewportGridService, displaySetService) {
let { viewports } = viewportGridService.getState();
viewports = [...viewports.values()];
// filter empty viewports
viewports = viewports.filter(
viewport => viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length
);
// filter reconstructable viewports
viewports = viewports.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
// TODO - add a better test than isReconstructable
if (displaySet && displaySet.isReconstructable) {
return true;
}
return false;
}
});
return viewports;
}
+329
View File
@@ -3,6 +3,335 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
### Bug Fixes
* **display messages:** broken after timings ([#3719](https://github.com/OHIF/Viewers/issues/3719)) ([157b88c](https://github.com/OHIF/Viewers/commit/157b88c909d3289cb89ace731c1f9a19d40797ac))
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
### Bug Fixes
* **export:** wrong export for the tmtv RT function ([#3715](https://github.com/OHIF/Viewers/issues/3715)) ([a3f2a1a](https://github.com/OHIF/Viewers/commit/a3f2a1a7b0d16bfcc0ecddc2ab731e54c5e377c8))
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
### Bug Fixes
* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07))
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
### Features
* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6))
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
### Features
* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799))
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
### Bug Fixes
* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9))
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
### Bug Fixes
* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5))
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-default
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-default",
"version": "3.7.0-beta.75",
"version": "3.8.0-beta.2",
"description": "Common/default features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@@ -30,8 +30,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.75",
"@ohif/i18n": "3.7.0-beta.75",
"@ohif/core": "3.8.0-beta.2",
"@ohif/i18n": "3.8.0-beta.2",
"dcmjs": "^0.29.5",
"dicomweb-client": "^0.10.2",
"prop-types": "^15.6.2",
@@ -4,49 +4,31 @@ import { DicomMetadataStore } from '@ohif/core';
/**
*
* @param {*} servicesManager
* @param {*} dataSource
* @param {*} measurements
* @param {*} options
* @returns {string[]} displaySetInstanceUIDs
*/
async function createReportAsync(
servicesManager,
commandsManager,
dataSource,
measurements,
options
) {
async function createReportAsync({ servicesManager, getReport, reportType = 'measurement' }) {
const { displaySetService, uiNotificationService, uiDialogService } = servicesManager.services;
const loadingDialogId = uiDialogService.create({
showOverlay: true,
isDraggable: false,
centralize: true,
// TODO: Create a loading indicator component + zeplin design?
content: Loading,
});
try {
const naturalizedReport = await commandsManager.runCommand(
'storeMeasurements',
{
measurementData: measurements,
dataSource,
additionalFindingTypes: ['ArrowAnnotate'],
options,
},
'CORNERSTONE_STRUCTURED_REPORT'
);
const naturalizedReport = await getReport();
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
DicomMetadataStore.addInstances([naturalizedReport], true);
const displaySetInstanceUID = displaySetService.getMostRecentDisplaySet();
const displaySet = displaySetService.getMostRecentDisplaySet();
const displaySetInstanceUID = displaySet.displaySetInstanceUID;
uiNotificationService.show({
title: 'Create Report',
message: 'Measurements saved successfully',
message: `${reportType} saved successfully`,
type: 'success',
});
@@ -54,7 +36,7 @@ async function createReportAsync(
} catch (error) {
uiNotificationService.show({
title: 'Create Report',
message: error.message || 'Failed to store measurements',
message: error.message || `Failed to store ${reportType}`,
type: 'error',
});
} finally {
@@ -0,0 +1,60 @@
import React, { useEffect, useState } from 'react';
import { SidePanel } from '@ohif/ui';
import { PanelService, ServicesManager } from '@ohif/core';
export type SidePanelWithServicesProps = {
servicesManager: ServicesManager;
side: 'left' | 'right';
className: string;
activeTabIndex: number;
tabs: any;
};
const SidePanelWithServices = ({
servicesManager,
side,
className,
activeTabIndex: activeTabIndexProp,
tabs,
}) => {
const panelService: PanelService = servicesManager?.services?.panelService;
// Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM.
// Thus going to the Study List page and back to the viewer resets this flag for a SidePanel.
const [hasBeenOpened, setHasBeenOpened] = useState(false);
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
useEffect(() => {
if (panelService) {
const activatePanelSubscription = panelService.subscribe(
panelService.EVENTS.ACTIVATE_PANEL,
(activatePanelEvent: Types.ActivatePanelEvent) => {
if (!hasBeenOpened || activatePanelEvent.forceActive) {
const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId);
if (tabIndex !== -1) {
setActiveTabIndex(tabIndex);
}
}
}
);
return () => {
activatePanelSubscription.unsubscribe();
};
}
}, [tabs, hasBeenOpened, panelService]);
return (
<SidePanel
side={side}
className={className}
activeTabIndex={activeTabIndex}
tabs={tabs}
onOpen={() => {
setHasBeenOpened(true);
}}
></SidePanel>
);
};
export default SidePanelWithServices;
@@ -119,18 +119,18 @@ function createDicomJSONApi(dicomJsonConfig) {
});
},
processResults: () => {
console.debug(' DICOMJson QUERY processResults');
console.warn(' DICOMJson QUERY processResults not implemented');
},
},
series: {
// mapParams: mapParams.bind(),
search: () => {
console.debug(' DICOMJson QUERY SERIES SEARCH');
console.warn(' DICOMJson QUERY SERIES SEARCH not implemented');
},
},
instances: {
search: () => {
console.debug(' DICOMJson QUERY instances SEARCH');
console.warn(' DICOMJson QUERY instances SEARCH not implemented');
},
},
},
@@ -211,7 +211,7 @@ function createDicomJSONApi(dicomJsonConfig) {
},
store: {
dicom: () => {
console.debug(' DICOMJson store dicom');
console.warn(' DICOMJson store dicom not implemented');
},
},
getImageIdsForDisplaySet(displaySet) {
@@ -85,7 +85,7 @@ function createDicomLocalApi(dicomLocalConfig) {
});
},
processResults: () => {
console.debug(' DICOMLocal QUERY processResults');
console.warn(' DICOMLocal QUERY processResults not implemented');
},
},
series: {
@@ -107,7 +107,7 @@ function createDicomLocalApi(dicomLocalConfig) {
},
instances: {
search: () => {
console.debug(' DICOMLocal QUERY instances SEARCH');
console.warn(' DICOMLocal QUERY instances SEARCH not implemented');
},
},
},
@@ -221,7 +221,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
await wadoDicomWebClient.storeInstances(options);
} else {
const meta = {
FileMetaInformationVersion: dataset._meta.FileMetaInformationVersion.Value,
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
MediaStorageSOPClassUID: dataset.SOPClassUID,
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
@@ -295,6 +295,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
});
instance.imageId = imageId;
instance.wadoRoot = dicomWebConfig.wadoRoot;
instance.wadoUri = dicomWebConfig.wadoUri;
metadataProvider.addImageIdToUIDs(imageId, {
StudyInstanceUID,
@@ -160,7 +160,7 @@ export default class StaticWadoClient extends api.DICOMwebClient {
if (!valueElem) {
return false;
}
if (valueElem.vr == 'DA') {
if (valueElem.vr === 'DA' && valueElem.Value?.[0]) {
return this.compareDateRange(testValue, valueElem.Value[0]);
}
const value = valueElem.Value;
@@ -2,18 +2,18 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { LegacyButton, ButtonGroup } from '@ohif/ui';
import { LegacyButton, LegacyButtonGroup } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick }) {
const { t } = useTranslation('MeasurementTable');
return (
<React.Fragment>
<ButtonGroup
<LegacyButtonGroup
color="black"
size="inherit"
>
{/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/}
{/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/}
<LegacyButton
className="px-2 py-2 text-base"
onClick={onExportClick}
@@ -26,7 +26,7 @@ function ActionButtons({ onExportClick, onCreateReportClick }) {
>
{t('Create Report')}
</LegacyButton>
</ButtonGroup>
</LegacyButtonGroup>
</React.Fragment>
);
}
@@ -103,13 +103,20 @@ export default function PanelMeasurementTable({
// creating too many series instances.
const options = findSRWithSameSeriesDescription(SeriesDescription, displaySetService);
return createReportAsync(
servicesManager,
commandsManager,
dataSource,
trackedMeasurements,
options
);
const getReport = async () => {
return commandsManager.runCommand(
'storeMeasurements',
{
measurementData: trackedMeasurements,
dataSource,
additionalFindingTypes: ['ArrowAnnotate'],
options,
},
'CORNERSTONE_STRUCTURED_REPORT'
);
};
return createReportAsync({ servicesManager, getReport });
}
}
@@ -33,7 +33,6 @@ function PanelStudyBrowser({
const [studyDisplayList, setStudyDisplayList] = useState([]);
const [displaySets, setDisplaySets] = useState([]);
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({});
const isMounted = useRef(true);
const onDoubleClickThumbnailHandler = displaySetInstanceUID => {
let updatedViewports = [];
@@ -103,8 +102,7 @@ function PanelStudyBrowser({
}
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [StudyInstanceUIDs, getStudiesForPatientByMRN]);
}, [StudyInstanceUIDs, dataSource, getStudiesForPatientByMRN, navigate]);
// // ~~ Initial Thumbnails
useEffect(() => {
@@ -116,21 +114,17 @@ function PanelStudyBrowser({
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaySets?
if (imageId && !displaySet?.unsupported) {
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
if (isMounted.current) {
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
}
if (!imageId || displaySet?.unsupported) {
return;
}
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
return () => {
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [StudyInstanceUIDs, dataSource, displaySetService, getImageSrc]);
// ~~ displaySets
useEffect(() => {
@@ -140,8 +134,7 @@ function PanelStudyBrowser({
sortStudyInstances(mappedDisplaySets);
setDisplaySets(mappedDisplaySets);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [thumbnailImageSrcMap]);
}, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]);
// ~~ subscriptions --> displaySets
useEffect(() => {
@@ -149,32 +142,40 @@ function PanelStudyBrowser({
const SubscriptionDisplaySetsAdded = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
data => {
const { displaySetsAdded } = data;
const { displaySetsAdded, options } = data;
displaySetsAdded.forEach(async dSet => {
const newImageSrcEntry = {};
const displaySet = displaySetService.getDisplaySetByUID(dSet.displaySetInstanceUID);
if (!displaySet?.unsupported) {
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaysets?
if (imageId) {
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(
imageId,
dSet.initialViewport
);
if (isMounted.current) {
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
}
}
if (displaySet?.unsupported) {
return;
}
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaysets?
if (!imageId) {
return;
}
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(
imageId,
dSet.initialViewport
);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
}
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
};
}, [getImageSrc, dataSource, displaySetService]);
useEffect(() => {
// TODO: Will this always hold _all_ the displaySets we care about?
// DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets`
const SubscriptionDisplaySetsChanged = displaySetService.subscribe(
@@ -198,12 +199,10 @@ function PanelStudyBrowser({
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
SubscriptionDisplaySetsChanged.unsubscribe();
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]);
const tabs = _createStudyBrowserTabs(StudyInstanceUIDs, studyDisplayList, displaySets);
@@ -1,5 +1,5 @@
/* eslint-disable react/display-name */
import React from 'react';
import { ButtonEnums, Dialog, Input, Select } from '@ohif/ui';
export const CREATE_REPORT_DIALOG_RESPONSE = {
@@ -7,7 +7,7 @@ export const CREATE_REPORT_DIALOG_RESPONSE = {
CREATE_REPORT: 1,
};
export default function createReportDialogPrompt(uiDialogService, { extensionManager }) {
export default function CreateReportDialogPrompt(uiDialogService, { extensionManager }) {
return new Promise(function (resolve, reject) {
let dialogId = undefined;
@@ -97,33 +97,38 @@ export default function createReportDialogPrompt(uiDialogService, { extensionMan
};
return (
<>
{dataSourcesOpts.length > 1 && (
<Select
closeMenuOnSelect={true}
className="border-primary-main mr-2 bg-black"
options={dataSourcesOpts}
placeholder={
dataSourcesOpts.find(option => option.value === value.dataSourceName)
.placeHolder
}
value={value.dataSourceName}
onChange={evt => {
setValue(v => ({ ...v, dataSourceName: evt.value }));
}}
isClearable={false}
/>
{dataSourcesOpts.length > 1 && window.config?.allowMultiSelectExport && (
<div>
<label className="text-[14px] leading-[1.2] text-white">Data Source</label>
<Select
closeMenuOnSelect={true}
className="border-primary-main mt-2 bg-black"
options={dataSourcesOpts}
placeholder={
dataSourcesOpts.find(option => option.value === value.dataSourceName)
.placeHolder
}
value={value.dataSourceName}
onChange={evt => {
setValue(v => ({ ...v, dataSourceName: evt.value }));
}}
isClearable={false}
/>
</div>
)}
<Input
autoFocus
label="Enter the report name"
labelClassName="text-white text-[14px] leading-[1.2]"
className="border-primary-main bg-black"
type="text"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
required
/>
<div className="mt-3">
<Input
autoFocus
label="Enter the report name"
labelClassName="text-white text-[14px] leading-[1.2]"
className="border-primary-main bg-black"
type="text"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
required
/>
</div>
</>
);
},
+7 -1
View File
@@ -1,5 +1,11 @@
import PanelStudyBrowser from './PanelStudyBrowser';
import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser';
import PanelMeasurementTable from './PanelMeasurementTable';
import createReportDialogPrompt from './createReportDialogPrompt';
export { PanelStudyBrowser, WrappedPanelStudyBrowser, PanelMeasurementTable };
export {
PanelStudyBrowser,
WrappedPanelStudyBrowser,
PanelMeasurementTable,
createReportDialogPrompt,
};
+10 -34
View File
@@ -1,51 +1,29 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import classnames from 'classnames';
export default function Toolbar({ servicesManager }) {
const { toolbarService } = servicesManager.services;
const [toolbarButtons, setToolbarButtons] = useState([]);
const [buttonState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
// Could track buttons and state separately...?
useEffect(() => {
const { unsubscribe: unsub1 } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_MODIFIED,
() => setToolbarButtons(toolbarService.getButtonSection('primary'))
);
const { unsubscribe: unsub2 } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
() => setButtonState({ ...toolbarService.state })
const { unsubscribe } = toolbarService.subscribe(toolbarService.EVENTS.TOOL_BAR_MODIFIED, () =>
setToolbarButtons(toolbarService.getButtonSection('primary'))
);
return () => {
unsub1();
unsub2();
unsubscribe();
};
}, [toolbarService]);
const onInteraction = useCallback(
args => toolbarService.recordInteraction(args),
[toolbarService]
);
return (
<>
{toolbarButtons.map(toolDef => {
const { id, Component, componentProps } = toolDef;
// TODO: ...
// isActive if:
// - id is primary?
// - id is in list of "toggled on"?
let isActive;
if (componentProps.type === 'toggle') {
isActive = buttonState.toggles[id];
}
// Also need... to filter list for splitButton, and set primary based on most recently clicked
// Also need to kill the radioGroup button's magic logic
// Everything should be reactive off these props, so commands can inform ToolbarService
// These can... Trigger toolbar events based on updates?
// Then sync using useEffect, or simply modify the state here?
return (
// The margin for separating the tools on the toolbar should go here and NOT in each individual component (button) item.
// This allows for the individual items to be included in other UI components where perhaps alternative margins are desired.
@@ -56,9 +34,7 @@ export default function Toolbar({ servicesManager }) {
<Component
id={id}
{...componentProps}
bState={buttonState}
isActive={isActive}
onInteraction={args => toolbarService.recordInteraction(args)}
onInteraction={onInteraction}
servicesManager={servicesManager}
/>
</div>
@@ -0,0 +1,75 @@
import { ToolbarButton } from '@ohif/ui';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
function ToolbarButtonWithServices({
id,
type,
commands,
onInteraction,
servicesManager,
...props
}) {
const { toolbarService } = servicesManager?.services || {};
const [buttonsState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
const { primaryToolId } = buttonsState;
const isActive =
(type === 'tool' && id === primaryToolId) ||
(type === 'toggle' && buttonsState.toggles[id] === true);
useEffect(() => {
const { unsubscribe } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
state => {
setButtonState({ ...state });
}
);
return () => {
unsubscribe();
};
}, [toolbarService]);
return (
<ToolbarButton
commands={commands}
id={id}
type={type}
isActive={isActive}
onInteraction={onInteraction}
{...props}
/>
);
}
ToolbarButtonWithServices.propTypes = {
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
commands: PropTypes.arrayOf(
PropTypes.shape({
commandName: PropTypes.string.isRequired,
context: PropTypes.string,
})
),
onInteraction: PropTypes.func.isRequired,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
state: PropTypes.shape({
primaryToolId: PropTypes.string,
toggles: PropTypes.objectOf(PropTypes.bool),
groups: PropTypes.objectOf(PropTypes.object),
}).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
export default ToolbarButtonWithServices;
@@ -1,13 +1,37 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
import { ServicesManager } from '@ohif/core';
function LayoutSelector({ rows, columns, className, servicesManager, ...rest }) {
const [isOpen, setIsOpen] = useState(false);
function ToolbarLayoutSelectorWithServices({ servicesManager, ...props }) {
const { toolbarService } = servicesManager.services;
const { hangingProtocolService, toolbarService } = (servicesManager as ServicesManager).services;
const onSelection = useCallback(
props => {
toolbarService.recordInteraction({
interactionType: 'action',
commands: [
{
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
context: 'DEFAULT',
},
],
});
},
[toolbarService]
);
return (
<LayoutSelector
{...props}
onSelection={onSelection}
/>
);
}
function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
const [isOpen, setIsOpen] = useState(false);
const closeOnOutsideClick = () => {
if (isOpen) {
@@ -15,19 +39,6 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
}
};
useEffect(() => {
const { unsubscribe } = hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
evt => {
const { protocol } = evt;
}
);
return () => {
unsubscribe();
};
}, [hangingProtocolService]);
useEffect(() => {
window.addEventListener('click', closeOnOutsideClick);
return () => {
@@ -38,19 +49,6 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
const onInteractionHandler = () => setIsOpen(!isOpen);
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
const onSelectionHandler = props => {
toolbarService.recordInteraction({
interactionType: 'action',
commands: [
{
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
context: 'DEFAULT',
},
],
});
};
return (
<ToolbarButton
id="Layout"
@@ -64,7 +62,7 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
<DropdownContent
rows={rows}
columns={columns}
onSelection={onSelectionHandler}
onSelection={onSelection}
/>
)
}
@@ -87,4 +85,4 @@ LayoutSelector.defaultProps = {
onLayoutChange: () => {},
};
export default LayoutSelector;
export default ToolbarLayoutSelectorWithServices;
@@ -1,3 +0,0 @@
import { SplitButton } from '@ohif/ui';
export default SplitButton;
@@ -0,0 +1,186 @@
import { SplitButton, Icon, ToolbarButton } from '@ohif/ui';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
function ToolbarSplitButtonWithServices({
isRadio,
isAction,
groupId,
primary,
secondary,
items,
renderer,
onInteraction,
servicesManager,
}) {
const { toolbarService } = servicesManager?.services;
const handleItemClick = (item, index) => {
const { id, type, commands } = item;
onInteraction({
groupId,
itemId: id,
interactionType: type,
commands,
});
setState(state => ({
...state,
primary: !isAction && isRadio ? { ...item, index } : state.primary,
isExpanded: false,
items: getSplitButtonItems(items).filter(item =>
isRadio && !isAction ? item.index !== index : true
),
}));
};
/* Bubbles up individual item clicks */
const getSplitButtonItems = items =>
items.map((item, index) => ({
...item,
index,
onClick: () => handleItemClick(item, index),
}));
const [buttonsState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
const [state, setState] = useState({
primary,
items: getSplitButtonItems(items).filter(item =>
isRadio && !isAction ? item.id !== primary.id : true
),
});
const { primaryToolId, toggles } = buttonsState;
const isPrimaryToggle = state.primary.type === 'toggle';
const isPrimaryActive =
(state.primary.type === 'tool' && primaryToolId === state.primary.id) ||
(isPrimaryToggle && toggles[state.primary.id] === true);
const PrimaryButtonComponent =
toolbarService?.getButtonComponentForUIType(state.primary.uiType) ?? ToolbarButton;
useEffect(() => {
const { unsubscribe } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
state => {
setButtonState({ ...state });
}
);
return () => {
unsubscribe();
};
}, [toolbarService]);
const updatedItems = state.items.map(item => {
const isActive = item.type === 'tool' && primaryToolId === item.id;
// We could have added the
// item.type === 'toggle' && toggles[item.id] === true
// too but that makes the button active when the toggle is active under it
// which feels weird
return {
...item,
isActive,
};
});
const DefaultListItemRenderer = ({ type, icon, label, t, id }) => {
const isActive = type === 'toggle' && toggles[id] === true;
return (
<div
className={classNames(
'hover:bg-primary-dark flex h-8 w-full flex-row items-center p-3',
'whitespace-pre text-base',
isActive && 'bg-primary-dark',
isActive
? 'text-[#348CFD]'
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light'
)}
>
{icon && (
<span className="mr-4">
<Icon
name={icon}
className="h-5 w-5"
/>
</span>
)}
<span className="mr-5">{t(label)}</span>
</div>
);
};
const listItemRenderer = renderer || DefaultListItemRenderer;
return (
<SplitButton
isRadio={isRadio}
isAction={isAction}
primary={state.primary}
secondary={secondary}
items={updatedItems}
groupId={groupId}
renderer={listItemRenderer}
isActive={isPrimaryActive || updatedItems.some(item => item.isActive)}
isToggle={isPrimaryToggle}
onInteraction={onInteraction}
Component={props => (
<PrimaryButtonComponent
{...props}
servicesManager={servicesManager}
/>
)}
/>
);
}
ToolbarSplitButtonWithServices.propTypes = {
isRadio: PropTypes.bool,
isAction: PropTypes.bool,
groupId: PropTypes.string,
primary: PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
uiType: PropTypes.string,
}),
secondary: PropTypes.shape({
id: PropTypes.string,
icon: PropTypes.string.isRequired,
label: PropTypes.string,
tooltip: PropTypes.string.isRequired,
isActive: PropTypes.bool,
}),
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
icon: PropTypes.string,
label: PropTypes.string,
tooltip: PropTypes.string,
})
),
renderer: PropTypes.func,
onInteraction: PropTypes.func.isRequired,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.object,
}),
}),
};
ToolbarSplitButtonWithServices.defaultProps = {
isRadio: false,
isAction: false,
};
export default ToolbarSplitButtonWithServices;
@@ -5,6 +5,7 @@ import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui';
import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core';
import { useAppConfig } from '@state';
import ViewerHeader from './ViewerHeader';
import SidePanelWithServices from '../Components/SidePanelWithServices';
function ViewerLayout({
// From Extension Module Params
@@ -44,7 +45,7 @@ function ViewerLayout({
if (!entry) {
throw new Error(
`${id} is not a valid entry for an extension module, please check your configuration or make sure the extension is registered.`
`${id} is not valid for an extension module. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file)`
);
}
@@ -119,7 +120,7 @@ function ViewerLayout({
{/* LEFT SIDEPANELS */}
{leftPanelComponents.length ? (
<ErrorBoundary context="Left Panel">
<SidePanel
<SidePanelWithServices
side="left"
activeTabIndex={leftPanelDefaultClosed ? null : 0}
tabs={leftPanelComponents}
@@ -141,7 +142,7 @@ function ViewerLayout({
</div>
{rightPanelComponents.length ? (
<ErrorBoundary context="Right Panel">
<SidePanel
<SidePanelWithServices
side="right"
activeTabIndex={rightPanelDefaultClosed ? null : 0}
tabs={rightPanelComponents}
+48 -33
View File
@@ -134,7 +134,7 @@ const commandsModule = ({
(!protocolId || protocolId === protocol.id) &&
(stageIndex === undefined || stageIndex === toggleStageIndex) &&
(!stageId || stageId === stage.id);
toolbarService.setActive(button.id, isActive);
toolbarService.setToggled(button.id, isActive);
};
Object.values(toolbarService.getButtons()).forEach(enableListener);
},
@@ -170,6 +170,7 @@ const commandsModule = ({
stageIndex,
reset = false,
}: HangingProtocolParams): boolean => {
const primaryToolBeforeHPChange = toolbarService.getActivePrimaryTool();
try {
// Stores in the state the display set selector id to displaySetUID mapping
// Pass in viewportId for the active viewport. This item will get set as
@@ -241,7 +242,29 @@ const commandsModule = ({
stateSyncService.store(stateSyncReduce);
// This is a default action applied
const { protocol } = hangingProtocolService.getActiveProtocol();
actions.toggleHpTools(protocol);
actions.toggleHpTools();
// try to use the same tool in the new hanging protocol stage
const primaryButton = toolbarService.getButton(primaryToolBeforeHPChange);
if (primaryButton) {
// is there any type of interaction on this button, if not it might be in the
// items. This is a bit of a hack, but it works for now.
let interactionType = primaryButton.props?.interactionType;
if (!interactionType && primaryButton.props?.items) {
const firstItem = primaryButton.props.items[0];
interactionType = firstItem.props?.interactionType || firstItem.props?.type;
}
if (interactionType) {
toolbarService.recordInteraction({
interactionType,
...primaryButton.props,
});
}
}
// Send the notification about updating the state
if (protocolId !== hpInfo.protocolId) {
// The old protocol callbacks are used for turning off things
@@ -253,7 +276,8 @@ const commandsModule = ({
commandsManager.run(protocol.callbacks?.onProtocolEnter);
return true;
} catch (e) {
actions.toggleHpTools(hangingProtocolService.getActiveProtocol());
console.error(e);
actions.toggleHpTools();
uiNotificationService.show({
title: 'Apply Hanging Protocol',
message: 'The hanging protocol could not be applied.',
@@ -371,6 +395,11 @@ const commandsModule = ({
// originally toggled to one up was the former active viewport.
const viewportIdToUpdate = toggleOneUpViewportGridStore.activeViewportId;
// We are restoring the previous layout but taking into the account that
// the current one up viewport might have a new displaySet dragged and dropped on it.
// updatedViewportsViaHP below contains the viewports applicable to the HP that existed
// prior to the toggle to one-up - including the updated viewports if a display
// set swap were to have occurred.
const updatedViewportsViaHP =
displaySetInstanceUIDs.length > 1
? []
@@ -383,39 +412,25 @@ const commandsModule = ({
)
.flat();
// This findOrCreateViewport returns either one of the updatedViewports
// findOrCreateViewport returns either one of the updatedViewportsViaHP
// returned from the HP service OR if there is not one from the HP service then
// simply returns what was in the previous state.
const findOrCreateViewport = (position: number) => {
// get the viewportId in the current state (since we are in the one-up layout)
const currentOneUpViewport = Array.from(viewports.values())[0];
// we should restore the previous layout but take into the account the fact that
// the current one up viewport might have a new displaySet dragged and dropped on it
// so we should prioritize the current one in the old grid store layout viewports
const newViewports = Array.from(toggleOneUpViewportGridStore.viewports.values()).map(
viewport => {
if (viewport.viewportId === currentOneUpViewport.viewportId) {
return {
...currentOneUpViewport,
};
}
return viewport;
}
// simply returns what was in the previous state for a given position in the layout.
const findOrCreateViewport = (position: number, positionId: string) => {
// Find the viewport for the given position prior to the toggle to one-up.
const preOneUpViewport = Array.from(toggleOneUpViewportGridStore.viewports.values()).find(
viewport => viewport.positionId === positionId
);
// However, we also need to take into account that the current one up viewport
// might have been part of a bigger hanging protocol layout, so going back
// from one up we should apply those viewports as well.
return updatedViewportsViaHP.length > 1 && updatedViewportsViaHP[position]
? {
viewportOptions,
displaySetOptions,
...updatedViewportsViaHP[position],
}
: newViewports[position];
// Use the viewport id from before the toggle to one-up to find any updates to the viewport.
const viewport = updatedViewportsViaHP.find(
viewport => viewport.viewportId === preOneUpViewport.viewportId
);
return viewport
? // Use the applicable viewport from the HP updated viewports
{ viewportOptions, displaySetOptions, ...viewport }
: // Use the previous viewport for the given position
preOneUpViewport;
};
const layoutOptions = viewportGridService.getLayoutOptionsFromState(
@@ -15,20 +15,21 @@ export default function getDisplaySetMessages(
const messages = new DisplaySetMessageList();
if (!instances.length) {
messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES);
return;
}
const firstInstance = instances[0];
const { Modality, ImageType, NumberOfFrames } = firstInstance;
// Due to current requirements, LOCALIZER series doesn't have any messages
if (firstInstance.ImageType.includes('LOCALIZER')) {
if (ImageType?.includes('LOCALIZER')) {
return messages;
}
const Modality = firstInstance.Modality;
if (!constructableModalities.includes(Modality)) {
return messages;
}
const isMultiframe = firstInstance.NumberOfFrames > 1;
const isMultiframe = NumberOfFrames > 1;
// Can't reconstruct if all instances don't have the ImagePositionPatient.
if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) {
messages.addMessage(DisplaySetMessage.CODES.NO_POSITION_INFORMATION);
+1 -1
View File
@@ -20,7 +20,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
return [
{
name: 'seriesList',
iconName: 'group-layers',
iconName: 'tab-studies',
iconLabel: 'Studies',
label: 'Studies',
component: WrappedPanelStudyBrowser.bind(null, {
+9 -9
View File
@@ -1,7 +1,7 @@
import { ToolbarButton } from '@ohif/ui';
import ToolbarDivider from './Toolbar/ToolbarDivider.tsx';
import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.tsx';
import ToolbarSplitButton from './Toolbar/ToolbarSplitButton.tsx';
import ToolbarDivider from './Toolbar/ToolbarDivider';
import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector';
import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices';
import ToolbarButtonWithServices from './Toolbar/ToolbarButtonWithServices';
export default function getToolbarModule({ commandsManager, servicesManager }) {
return [
@@ -12,27 +12,27 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
},
{
name: 'ohif.action',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.radioGroup',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.splitButton',
defaultComponent: ToolbarSplitButton,
defaultComponent: ToolbarSplitButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.layoutSelector',
defaultComponent: ToolbarLayoutSelector,
defaultComponent: ToolbarLayoutSelectorWithServices,
clickHandler: (evt, clickedBtn, btnSectionName) => {},
},
{
name: 'ohif.toggle',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
];
+1
View File
@@ -29,6 +29,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
constraint: {
greaterThan: { value: 0 },
},
required: true,
},
// This display set will select the specified items by preference
// It has no affect if nothing is specified in the URL.
+4
View File
@@ -13,6 +13,8 @@ import { id } from './id.js';
import preRegistration from './init';
import { ContextMenuController, CustomizableContextMenuTypes } from './CustomizableContextMenu';
import * as dicomWebUtils from './DicomWebDataSource/utils';
import { createReportDialogPrompt } from './Panels';
import createReportAsync from './Actions/createReportAsync';
const defaultExtension: Types.Extensions.Extension = {
/**
@@ -48,4 +50,6 @@ export {
CustomizableContextMenuTypes,
getStudiesForPatientByMRN,
dicomWebUtils,
createReportDialogPrompt,
createReportAsync,
};
+2 -1
View File
@@ -6,5 +6,6 @@
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"arrowParens": "avoid"
"arrowParens": "avoid",
"endOfLine": "auto"
}
+313
View File
@@ -3,6 +3,319 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04)
### Bug Fixes
* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155))
# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03)
### Bug Fixes
* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e))
# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22)
### Features
* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1))
# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
Loaded 100 of 401 files, more files were not shown because too many files have changed in this diff. Show more