feat(v3p7): segmentation mode and cloud server configuration (#3720)
This commit is contained in:
commit
5ddf8a1602
@ -13,22 +13,24 @@ version: 2.1
|
|||||||
##
|
##
|
||||||
orbs:
|
orbs:
|
||||||
codecov: codecov/codecov@1.0.5
|
codecov: codecov/codecov@1.0.5
|
||||||
cypress: cypress-io/cypress@1.26.0
|
cypress: cypress-io/cypress@3.1.4
|
||||||
|
|
||||||
executors:
|
executors:
|
||||||
# Custom executor to override Cypress config
|
cypress-custom:
|
||||||
deploy-to-prod-executor:
|
description: |
|
||||||
|
Single Docker container used to run Cypress Tests
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.14
|
- image: cimg/node:<< parameters.node-version >>-browsers
|
||||||
environment:
|
parameters:
|
||||||
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
|
node-version:
|
||||||
chrome-and-pacs:
|
default: '18.16.1'
|
||||||
docker:
|
description: |
|
||||||
# Primary container image where all steps run.
|
The version of Node to run your tests with.
|
||||||
- image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102'
|
type: string
|
||||||
|
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.14-browsers
|
- image: cimg/node:18.18
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm # Enable colors in term
|
TERM: xterm # Enable colors in term
|
||||||
QUICK_BUILD: true
|
QUICK_BUILD: true
|
||||||
@ -43,6 +45,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# Update yarn
|
# Update yarn
|
||||||
- run: yarn -v
|
- run: yarn -v
|
||||||
|
- run: node --version
|
||||||
# Checkout code and ALL Git Tags
|
# Checkout code and ALL Git Tags
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@ -90,43 +93,6 @@ jobs:
|
|||||||
file: '/home/circleci/repo/platform/core/coverage/reports'
|
file: '/home/circleci/repo/platform/core/coverage/reports'
|
||||||
flags: 'core'
|
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
|
# Workflow: DEPLOY
|
||||||
###
|
###
|
||||||
@ -177,62 +143,52 @@ jobs:
|
|||||||
- commit.txt
|
- commit.txt
|
||||||
- version.json
|
- version.json
|
||||||
|
|
||||||
# DEPLOY_TO_DEV:
|
# just to make sure later on we can publish them
|
||||||
# docker:
|
BUILD_PACKAGES_QUICK:
|
||||||
# - image: circleci/node:16.14.0
|
<<: *defaults
|
||||||
# environment:
|
steps:
|
||||||
# TERM: xterm
|
- run: yarn -v
|
||||||
# NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
|
# Checkout code and ALL Git Tags
|
||||||
# working_directory: ~/repo
|
- checkout
|
||||||
# steps:
|
- attach_workspace:
|
||||||
# - attach_workspace:
|
at: ~/repo
|
||||||
# at: ~/repo
|
# Use increasingly general patterns to restore cache
|
||||||
# - run: cd .netlify && npm install
|
- restore_cache:
|
||||||
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
|
name: Restore Yarn and Cypress Package Cache
|
||||||
# - run: cd .netlify && npm run deploy
|
keys:
|
||||||
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
# DEPLOY_TO_STAGING:
|
- yarn-packages-
|
||||||
# docker:
|
- run:
|
||||||
# - image: circleci/node:16.14.0
|
name: Install Dependencies
|
||||||
# environment:
|
command: yarn install --frozen-lockfile
|
||||||
# TERM: xterm
|
- save_cache:
|
||||||
# NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
|
name: Save Yarn Package Cache
|
||||||
# working_directory: ~/repo
|
paths:
|
||||||
# steps:
|
- ~/.cache/yarn
|
||||||
# - attach_workspace:
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
# at: ~/repo
|
- run:
|
||||||
# - run: cd .netlify && npm install
|
name: Avoid hosts unknown for github
|
||||||
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
|
command: |
|
||||||
# - run: cd .netlify && npm run deploy
|
rm -rf ~/.ssh
|
||||||
|
mkdir ~/.ssh/
|
||||||
# DEPLOY_TO_PRODUCTION:
|
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
|
||||||
# docker:
|
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
||||||
# - image: circleci/node:16.14.0
|
git config --global user.name "ohif-bot"
|
||||||
# environment:
|
- run:
|
||||||
# TERM: xterm
|
name: Authenticate with NPM registry
|
||||||
# NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
# working_directory: ~/repo
|
- run:
|
||||||
# steps:
|
name: Increase the event emitter limit
|
||||||
# - attach_workspace:
|
command: |
|
||||||
# at: ~/repo
|
node ./increaseEventEmitterLimit.mjs
|
||||||
# - run: cd .netlify && npm install
|
- run:
|
||||||
# - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects
|
name: build half of the packages (to avoid out of memory in circleci)
|
||||||
# - run: cd .netlify && npm run deploy
|
command: |
|
||||||
|
yarn run build:package-all
|
||||||
# DEPLOY_TO_RELEASE_DEV:
|
- run:
|
||||||
# docker:
|
name: build the other half of the packages
|
||||||
# - image: circleci/node:16.14.0
|
command: |
|
||||||
# environment:
|
yarn run build:package-all-1
|
||||||
# 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
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Workflow: RELEASE
|
# Workflow: RELEASE
|
||||||
@ -269,8 +225,7 @@ jobs:
|
|||||||
git config --global user.name "ohif-bot"
|
git config --global user.name "ohif-bot"
|
||||||
- run:
|
- run:
|
||||||
name: Authenticate with NPM registry
|
name: Authenticate with NPM registry
|
||||||
command:
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
|
||||||
- run:
|
- run:
|
||||||
name: Increase the event emitter limit
|
name: Increase the event emitter limit
|
||||||
command: |
|
command: |
|
||||||
@ -283,14 +238,21 @@ jobs:
|
|||||||
name: build the other half of the packages
|
name: build the other half of the packages
|
||||||
command: |
|
command: |
|
||||||
yarn run build:package-all-1
|
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:
|
- run:
|
||||||
name: publish package versions
|
name: publish package versions
|
||||||
command: |
|
command: |
|
||||||
node ./publish-version.mjs
|
node ./publish-version.mjs
|
||||||
- run:
|
- run:
|
||||||
name: Again set the NPM registry (was deleted in the version script)
|
name: Again set the NPM registry (was deleted in the version script)
|
||||||
command:
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
|
||||||
- run:
|
- run:
|
||||||
name: publish package dist
|
name: publish package dist
|
||||||
command: |
|
command: |
|
||||||
@ -337,8 +299,7 @@ jobs:
|
|||||||
- setup_remote_docker:
|
- setup_remote_docker:
|
||||||
docker_layer_caching: false
|
docker_layer_caching: false
|
||||||
- run:
|
- run:
|
||||||
name:
|
name: Build and push Docker image from the master branch (beta releases)
|
||||||
Build and push Docker image from the master branch (beta releases)
|
|
||||||
command: |
|
command: |
|
||||||
echo $(ls -l)
|
echo $(ls -l)
|
||||||
|
|
||||||
@ -365,71 +326,152 @@ jobs:
|
|||||||
docker push ohif/app:$IMAGE_VERSION_FULL
|
docker push ohif/app:$IMAGE_VERSION_FULL
|
||||||
fi
|
fi
|
||||||
|
|
||||||
workflows:
|
# This is copied from the Cypress orb since the default for cypress/run is node 16 and
|
||||||
version: 2
|
# 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:
|
PR_CHECKS:
|
||||||
jobs:
|
jobs:
|
||||||
- UNIT_TESTS
|
- BUILD_PACKAGES_QUICK:
|
||||||
|
filters:
|
||||||
# E2E: PWA
|
branches:
|
||||||
- cypress/run:
|
ignore: master
|
||||||
name: 'E2E: PWA'
|
- UNIT_TESTS:
|
||||||
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
|
|
||||||
requires:
|
requires:
|
||||||
- UNIT_TESTS
|
- BUILD_PACKAGES_QUICK
|
||||||
|
- CYPRESS_CUSTOM_RUN:
|
||||||
PR_OPTIONAL_VISUAL_TESTS:
|
name: 'Cypress Tests'
|
||||||
jobs:
|
context: cypress
|
||||||
- AWAIT_APPROVAL:
|
matrix:
|
||||||
type: approval
|
parameters:
|
||||||
# Update hub.docker.org
|
start-command:
|
||||||
- cypress/run:
|
- yarn run test:data && yarn run test:e2e:serve
|
||||||
name: 'Generate Percy Snapshots'
|
install-browsers:
|
||||||
executor: cypress/browsers-chrome76
|
- true
|
||||||
browser: chrome
|
cypress-command:
|
||||||
pre-steps:
|
- 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run
|
||||||
- run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
|
--record --browser chrome --parallel'
|
||||||
yarn: true
|
package-manager:
|
||||||
store_artifacts: false
|
- 'yarn'
|
||||||
working_directory: platform/app
|
cypress-cache-key:
|
||||||
build:
|
- 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
yarn test:data && npx cross-env QUICK_BUILD=true
|
cypress-cache-path:
|
||||||
APP_CONFIG=config/dicomweb-server.js yarn run build
|
- '~/.cache/Cypress'
|
||||||
# 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:
|
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
|
# 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.
|
# deployed from the release branch which is more stable and less frequently updated.
|
||||||
|
|||||||
6
.codespellrc
Normal file
6
.codespellrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[codespell]
|
||||||
|
skip = .git,*.pdf,*.svg,yarn.lock,*.min.js,locales
|
||||||
|
# ignore words ending with … and some camelcased variables and names
|
||||||
|
ignore-regex = \b\S+…\S*|\b(doubleClick|afterAll|PostgresSQL)\b|\bWee, L\.|.*te.*Telugu.*
|
||||||
|
# some odd variables
|
||||||
|
ignore-words-list = datea,ser,childrens
|
||||||
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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,5 +1,6 @@
|
|||||||
server {
|
server {
|
||||||
listen ${PORT};
|
listen ${PORT} default_server;
|
||||||
|
listen [::]:${PORT} default_server;
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
|
|||||||
22
.docker/Viewer-v3.x/default.ssl.conf.template
Normal file
22
.docker/Viewer-v3.x/default.ssl.conf.template
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
server {
|
||||||
|
listen ${SSL_PORT} ssl http2 default_server;
|
||||||
|
listen [::]:${SSL_PORT} ssl http2 default_server;
|
||||||
|
ssl_certificate /etc/ssl/certs/ssl-certificate.crt;
|
||||||
|
ssl_certificate_key /etc/ssl/private/ssl-private-key.key;
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
add_header Cross-Origin-Opener-Policy same-origin;
|
||||||
|
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||||
|
add_header Cross-Origin-Resource-Policy same-origin;
|
||||||
|
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-Proto $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,16 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf
|
if [ -n "$SSL_PORT" ]
|
||||||
|
then
|
||||||
|
envsubst '${SSL_PORT}:${PORT}' < /usr/src/default.ssl.conf.template > /etc/nginx/conf.d/default.conf
|
||||||
|
else
|
||||||
|
envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$APP_CONFIG" ]
|
||||||
|
then
|
||||||
|
echo "$APP_CONFIG" > /usr/share/nginx/html/app-config.js
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||||
then
|
then
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
# Reduces size of context and hides
|
# Reduces size of context and hides
|
||||||
# files from Docker (can't COPY or ADD these)
|
# files from Docker (can't COPY or ADD these)
|
||||||
|
|
||||||
|
# Note that typically the Docker context for various OHIF containers is the
|
||||||
|
# directory of this file (i.e. the root of the source). As such, this is
|
||||||
|
# the .dockerignore file for ALL Docker containers that are built. For example,
|
||||||
|
# the Docker containers built from the recipes in ./platform/app/.recipes will
|
||||||
|
# have this file as their .dockerignore.
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
@ -28,3 +34,4 @@ dockerfile
|
|||||||
.vscode/
|
.vscode/
|
||||||
coverage/
|
coverage/
|
||||||
docs/
|
docs/
|
||||||
|
testdata/
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": ["@typescript-eslint", "import", "eslint-plugin-tsdoc", "prettier"],
|
||||||
"@typescript-eslint",
|
|
||||||
"import",
|
|
||||||
"eslint-plugin-tsdoc",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"react-app",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
@ -21,6 +16,10 @@
|
|||||||
"version": "detect"
|
"version": "detect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rules": {
|
||||||
|
// Enforce consistent brace style for all control statements for readability
|
||||||
|
"curly": "error"
|
||||||
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"cy": true,
|
"cy": true,
|
||||||
"before": true,
|
"before": true,
|
||||||
|
|||||||
14
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
14
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -39,8 +39,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: The current behavior
|
label: The current behavior
|
||||||
description:
|
description:
|
||||||
'A clear and concise description of what happens instead of the expected
|
'A clear and concise description of what happens instead of the expected behavior.'
|
||||||
behavior.'
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -48,8 +47,7 @@ body:
|
|||||||
id: expected_behavior
|
id: expected_behavior
|
||||||
attributes:
|
attributes:
|
||||||
label: The expected behavior
|
label: The expected behavior
|
||||||
description:
|
description: 'A clear and concise description of what you expected to happen.'
|
||||||
'A clear and concise description of what you expected to happen.'
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -66,7 +64,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 'Node version'
|
label: 'Node version'
|
||||||
description: 'Your Node.js version.'
|
description: 'Your Node.js version.'
|
||||||
placeholder: 'e.g., 16.14.0'
|
placeholder: 'e.g., 18.16.1'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
@ -81,6 +79,6 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: >
|
value: >
|
||||||
> :warning: Reports we cannot reproduce are at risk of being marked
|
> :warning: Reports we cannot reproduce are at risk of being marked stale and > closed. The
|
||||||
stale and > closed. The more information you can provide, the more
|
more information you can provide, the more likely we are to look > into and address your
|
||||||
likely we are to look > into and address your issue.
|
issue.
|
||||||
|
|||||||
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@ -20,17 +20,15 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 'What feature or change would you like to see made?'
|
label: 'What feature or change would you like to see made?'
|
||||||
description:
|
description:
|
||||||
'Please include as much detail as possible including possibly mock up
|
'Please include as much detail as possible including possibly mock up screen shots, workflow
|
||||||
screen shots, workflow or logic flow diagrams etc.'
|
or logic flow diagrams etc.'
|
||||||
placeholder: '...'
|
placeholder: '...'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 'Why should we prioritize this feature?'
|
label: 'Why should we prioritize this feature?'
|
||||||
description:
|
description: 'Discuss if and how the requested feature interacts with existing features.'
|
||||||
'Discuss if and how the requested feature interacts with existing
|
|
||||||
features.'
|
|
||||||
placeholder: '...'
|
placeholder: '...'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -83,7 +83,7 @@ after the commits are squashed.
|
|||||||
#### Tested Environment
|
#### Tested Environment
|
||||||
|
|
||||||
- [] OS: <!--[e.g. Windows 10, macOS 10.15.4]-->
|
- [] OS: <!--[e.g. Windows 10, macOS 10.15.4]-->
|
||||||
- [] Node version: <!--[e.g. 16.14.0]-->
|
- [] Node version: <!--[e.g. 18.16.1]-->
|
||||||
- [] Browser:
|
- [] Browser:
|
||||||
<!--[e.g. Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1]-->
|
<!--[e.g. Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1]-->
|
||||||
|
|
||||||
|
|||||||
5
.github/stale.yml
vendored
5
.github/stale.yml
vendored
@ -19,8 +19,7 @@ exemptLabels:
|
|||||||
staleLabel: 'Stale :baguette_bread:'
|
staleLabel: 'Stale :baguette_bread:'
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
markComment: >
|
markComment: >
|
||||||
This issue has been automatically marked as stale because it has not had
|
This issue has been automatically marked as stale because it has not had recent activity. It will
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
be closed if no further activity occurs. Thank you for your contributions.
|
||||||
for your contributions.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
closeComment: false
|
closeComment: false
|
||||||
|
|||||||
22
.github/workflows/codespell.yml
vendored
Normal file
22
.github/workflows/codespell.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Codespell
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codespell:
|
||||||
|
name: Check for spelling errors
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Codespell
|
||||||
|
uses: codespell-project/actions-codespell@v2
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,3 +42,6 @@ platform/app/src/pluginImports.js
|
|||||||
/Viewers.iml
|
/Viewers.iml
|
||||||
platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/*
|
platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/*
|
||||||
platform/app/.recipes/OpenResty-Orthanc/logs/*
|
platform/app/.recipes/OpenResty-Orthanc/logs/*
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
.vs
|
||||||
|
|||||||
1
.gitmodules
vendored
1
.gitmodules
vendored
@ -1,3 +1,4 @@
|
|||||||
[submodule "testdata"]
|
[submodule "testdata"]
|
||||||
path = testdata
|
path = testdata
|
||||||
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
|
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
|
||||||
|
branch = main
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OHIF Viewer: Deploy Preview</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
<head>
|
<body>
|
||||||
<title>OHIF Viewer: Deploy Preview</title>
|
<h1>Index of Previews</h1>
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Index of Previews</h1>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/pwa">OHIF Viewer</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/docs">Documentation</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/ui">UI: Component Library</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/pwa">OHIF Viewer</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/docs">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/ui">UI: Component Library</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
16.14.0
|
18.16.1
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"],
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"printWidth": 80,
|
"printWidth": 100,
|
||||||
"proseWrap": "always",
|
"proseWrap": "always",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"arrowParens": "avoid"
|
"arrowParens": "avoid",
|
||||||
|
"singleAttributePerLine": true,
|
||||||
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ function excludeNodeModulesExcept(modules) {
|
|||||||
if (pathSep == '\\')
|
if (pathSep == '\\')
|
||||||
// must be quoted for use in a regexp:
|
// must be quoted for use in a regexp:
|
||||||
pathSep = '\\\\';
|
pathSep = '\\\\';
|
||||||
var moduleRegExps = modules.map(function(modName) {
|
var moduleRegExps = modules.map(function (modName) {
|
||||||
return new RegExp('node_modules' + pathSep + modName);
|
return new RegExp('node_modules' + pathSep + modName);
|
||||||
});
|
});
|
||||||
|
|
||||||
return function(modulePath) {
|
return function (modulePath) {
|
||||||
if (/node_modules/.test(modulePath)) {
|
if (/node_modules/.test(modulePath)) {
|
||||||
for (var i = 0; i < moduleRegExps.length; i++)
|
for (var i = 0; i < moduleRegExps.length; i++)
|
||||||
if (moduleRegExps[i].test(modulePath)) return false;
|
if (moduleRegExps[i].test(modulePath)) return false;
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
const autoprefixer = require('autoprefixer');
|
const autoprefixer = require('autoprefixer');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const tailwindcss = require('tailwindcss');
|
const tailwindcss = require('tailwindcss');
|
||||||
const tailwindConfigPath = path.resolve(
|
const tailwindConfigPath = path.resolve('../../platform/app/tailwind.config.js');
|
||||||
'../../platform/app/tailwind.config.js'
|
|
||||||
);
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const devMode = process.env.NODE_ENV !== 'production';
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
|||||||
10
.webpack/rules/stylusToJavaScript.js
Normal file
10
.webpack/rules/stylusToJavaScript.js
Normal 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;
|
||||||
@ -33,7 +33,11 @@ function transpileJavaScript(mode) {
|
|||||||
rootMode: 'upward',
|
rootMode: 'upward',
|
||||||
envName: mode,
|
envName: mode,
|
||||||
cacheCompression: false,
|
cacheCompression: false,
|
||||||
cacheDirectory: true,
|
// Note: This was causing a lot of issues with yarn link of the cornerstone
|
||||||
|
// only set this to true if you don't have a yarn link to external libs
|
||||||
|
// otherwise expect the lib changes not to be reflected in the dev server
|
||||||
|
// as it will be cached
|
||||||
|
cacheDirectory: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,7 @@ const fs = require('fs');
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
// ~~ PLUGINS
|
// ~~ PLUGINS
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
.BundleAnalyzerPlugin;
|
|
||||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
// ~~ PackageJSON
|
// ~~ PackageJSON
|
||||||
@ -19,6 +18,7 @@ const loadShadersRule = require('./rules/loadShaders.js');
|
|||||||
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||||
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
|
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
|
||||||
const cssToJavaScript = require('./rules/cssToJavaScript.js');
|
const cssToJavaScript = require('./rules/cssToJavaScript.js');
|
||||||
|
const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
|
||||||
|
|
||||||
// ~~ ENV VARS
|
// ~~ ENV VARS
|
||||||
const NODE_ENV = process.env.NODE_ENV;
|
const NODE_ENV = process.env.NODE_ENV;
|
||||||
@ -26,15 +26,34 @@ const QUICK_BUILD = process.env.QUICK_BUILD;
|
|||||||
const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0';
|
const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0';
|
||||||
|
|
||||||
// read from ../version.txt
|
// read from ../version.txt
|
||||||
const VERSION_NUMBER =
|
const VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || '';
|
||||||
fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || '';
|
|
||||||
|
|
||||||
const COMMIT_HASH =
|
const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || '';
|
||||||
fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || '';
|
|
||||||
|
|
||||||
//
|
//
|
||||||
dotenv.config();
|
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 }) => {
|
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
throw new Error('process.env.NODE_ENV not set');
|
throw new Error('process.env.NODE_ENV not set');
|
||||||
@ -73,21 +92,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
|||||||
children: false,
|
children: false,
|
||||||
warnings: true,
|
warnings: true,
|
||||||
},
|
},
|
||||||
devServer: {
|
|
||||||
open: true,
|
|
||||||
port: 3000,
|
|
||||||
historyApiFallback: true,
|
|
||||||
headers: {
|
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cache: {
|
cache: {
|
||||||
type: 'filesystem',
|
type: 'filesystem',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
noParse: [/(codec)/, /(dicomicc)/],
|
noParse: [/(codec)/, /(dicomicc)/],
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
enforce: 'pre',
|
||||||
|
use: 'source-map-loader',
|
||||||
|
},
|
||||||
transpileJavaScriptRule(mode),
|
transpileJavaScriptRule(mode),
|
||||||
loadWebWorkersRule,
|
loadWebWorkersRule,
|
||||||
// loadShadersRule,
|
// loadShadersRule,
|
||||||
@ -98,6 +113,9 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cssToJavaScript,
|
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/,
|
test: /\.wasm/,
|
||||||
type: 'asset/resource',
|
type: 'asset/resource',
|
||||||
@ -109,10 +127,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
|||||||
alias: {
|
alias: {
|
||||||
// Viewer project
|
// Viewer project
|
||||||
'@': path.resolve(__dirname, '../platform/app/src'),
|
'@': path.resolve(__dirname, '../platform/app/src'),
|
||||||
'@components': path.resolve(
|
'@components': path.resolve(__dirname, '../platform/app/src/components'),
|
||||||
__dirname,
|
|
||||||
'../platform/app/src/components'
|
|
||||||
),
|
|
||||||
'@hooks': path.resolve(__dirname, '../platform/app/src/hooks'),
|
'@hooks': path.resolve(__dirname, '../platform/app/src/hooks'),
|
||||||
'@routes': path.resolve(__dirname, '../platform/app/src/routes'),
|
'@routes': path.resolve(__dirname, '../platform/app/src/routes'),
|
||||||
'@state': path.resolve(__dirname, '../platform/app/src/state'),
|
'@state': path.resolve(__dirname, '../platform/app/src/state'),
|
||||||
@ -135,29 +150,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
|||||||
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'],
|
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'],
|
||||||
// symlinked resources are resolved to their real path, not their symlinked location
|
// symlinked resources are resolved to their real path, not their symlinked location
|
||||||
symlinks: true,
|
symlinks: true,
|
||||||
fallback: { fs: false, path: false, zlib: false },
|
fallback: {
|
||||||
|
fs: false,
|
||||||
|
path: false,
|
||||||
|
zlib: false,
|
||||||
|
buffer: require.resolve('buffer'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin(defineValues),
|
||||||
/* Application */
|
new webpack.ProvidePlugin({
|
||||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
Buffer: ['buffer', 'Buffer'],
|
||||||
'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 || ''
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
// Uncomment to generate bundle analyzer
|
// Uncomment to generate bundle analyzer
|
||||||
// new BundleAnalyzerPlugin(),
|
// new BundleAnalyzerPlugin(),
|
||||||
|
|||||||
565
CHANGELOG.md
Normal file
565
CHANGELOG.md
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **DicomJson:** retrieve.series.metadata method should be async ([#3659](https://github.com/OHIF/Viewers/issues/3659)) ([2737903](https://github.com/OHIF/Viewers/commit/2737903386cf97399473e0fa64fe53ad14da155a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **suv:** import calculate-suv library version that prevents SUV calculation for a zero PatientWeight ([#3638](https://github.com/OHIF/Viewers/issues/3638)) ([0d10f46](https://github.com/OHIF/Viewers/commit/0d10f46b885fe54ec3dae1848134da658eb6280a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **PT Metadata:** Allow for PatientWeight to be missing from the metadata ([#3621](https://github.com/OHIF/Viewers/issues/3621)) ([44f101d](https://github.com/OHIF/Viewers/commit/44f101d3f2b3204b67e31f4e4939062e65a246ee))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package ohif-monorepo-root
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8))
|
||||||
12
Dockerfile
12
Dockerfile
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
# Stage 1: Build the application
|
# Stage 1: Build the application
|
||||||
# docker build -t ohif/viewer:latest .
|
# 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
|
RUN mkdir /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@ -37,7 +37,8 @@ COPY platform /usr/src/app/platform
|
|||||||
#RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
|
#RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
|
||||||
|
|
||||||
# Copy Files
|
# 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
|
RUN mkdir /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ RUN yarn run build
|
|||||||
|
|
||||||
# Stage 3: Bundle the built application into a Docker container
|
# Stage 3: Bundle the built application into a Docker container
|
||||||
# which runs Nginx using Alpine Linux
|
# which runs Nginx using Alpine Linux
|
||||||
FROM nginxinc/nginx-unprivileged:1.23.1-alpine as final
|
FROM nginxinc/nginx-unprivileged:1.25-alpine as final
|
||||||
#RUN apk add --no-cache bash
|
#RUN apk add --no-cache bash
|
||||||
ENV PORT=80
|
ENV PORT=80
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
RUN rm /etc/nginx/conf.d/default.conf
|
||||||
@ -69,5 +70,10 @@ USER nginx
|
|||||||
COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src
|
COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src
|
||||||
RUN chmod 777 /usr/src/entrypoint.sh
|
RUN chmod 777 /usr/src/entrypoint.sh
|
||||||
COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html
|
COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html
|
||||||
|
# In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable.
|
||||||
|
# The nginx user cannot chmod it, so change to root.
|
||||||
|
USER root
|
||||||
|
RUN chmod 666 /usr/share/nginx/html/app-config.js
|
||||||
|
USER nginx
|
||||||
ENTRYPOINT ["/usr/src/entrypoint.sh"]
|
ENTRYPOINT ["/usr/src/entrypoint.sh"]
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
42
README.md
42
README.md
@ -3,7 +3,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>OHIF Medical Imaging Viewer</h1>
|
<h1>OHIF Medical Imaging Viewer</h1>
|
||||||
<p><strong>The OHIF Viewer</strong> is a zero-footprint medical image viewer
|
<p><strong>The OHIF Viewer</strong> is a zero-footprint medical image viewer
|
||||||
provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF)</a>. It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support <a href="https://www.dicomstandard.org/dicomweb/">DICOMweb</a>.</p>
|
provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF)</a>. It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support <a href="https://www.dicomstandard.org/using/dicomweb/">DICOMweb</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -38,7 +38,16 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
|
|||||||
<!-- [](#contributors) -->
|
<!-- [](#contributors) -->
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||

|
|
||||||
|
| | | |
|
||||||
|
| :-: | :--- | :--- |
|
||||||
|
| <img src="platform/docs/docs/assets/img/demo-measurements.jpg" alt="Measurement tracking" width="350"/> | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) |
|
||||||
|
| <img src="platform/docs/docs/assets/img/demo-segmentation.png" alt="Segmentations" width="350"/> | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) |
|
||||||
|
| <img src="platform/docs/docs/assets/img/demo-ptct.png" alt="Hanging Protocols" width="350"/> | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) |
|
||||||
|
| <img src="platform/docs/docs/assets/img/demo-microscopy.png" alt="Microscopy" width="350"/> | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) |
|
||||||
|
| <img src="platform/docs/docs/assets/img/demo-volumeRendering.png" alt="Volume Rendering" width="350"/> | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
@ -85,17 +94,14 @@ forking).
|
|||||||
|
|
||||||
### Support
|
### Support
|
||||||
|
|
||||||
We offer support through
|
|
||||||
[GitHub Issues](https://github.com/OHIF/Viewers/issues/new/choose). You can:
|
|
||||||
|
|
||||||
- [Report a Bug 🐛](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Report+%3Abug%3A%2CAwaiting+Reproduction&projects=&template=bug-report.yml&title=%5BBug%5D+)
|
- [Report a Bug 🐛](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Report+%3Abug%3A%2CAwaiting+Reproduction&projects=&template=bug-report.yml&title=%5BBug%5D+)
|
||||||
- [Request a Feature 🚀](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Request+%3Ahand%3A&projects=&template=feature-request.yml&title=%5BFeature+Request%5D+)
|
- [Request a Feature 🚀](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Request+%3Ahand%3A&projects=&template=feature-request.yml&title=%5BFeature+Request%5D+)
|
||||||
- [Ask a Question 🤗](community.ohif.org)
|
- [Ask a Question 🤗](community.ohif.org)
|
||||||
- [Slack Channel](https://join.slack.com/t/cornerstonejs/shared_invite/zt-1r8xb2zau-dOxlD6jit3TN0Uwf928w9Q)
|
- [Slack Channel](https://join.slack.com/t/cornerstonejs/shared_invite/zt-1r8xb2zau-dOxlD6jit3TN0Uwf928w9Q)
|
||||||
|
|
||||||
For commercial support, academic collaborations, and answers to common
|
For commercial support, academic collaborations, and answers to common
|
||||||
questions; please read our
|
questions; please use [Get Support](https://ohif.org/get-support/) to contact
|
||||||
[documented FAQ](https://docs.ohif.org/faq/index.html#does-ohif-offer-commercial-support).
|
us.
|
||||||
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
@ -128,9 +134,6 @@ Here is a schematic representation of our development workflow:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install)
|
- [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install)
|
||||||
@ -164,7 +167,7 @@ yarn install
|
|||||||
|
|
||||||
These commands are available from the root directory. Each project directory
|
These commands are available from the root directory. Each project directory
|
||||||
also supports a number of commands that can be found in their respective
|
also supports a number of commands that can be found in their respective
|
||||||
`README.md` and `project.json` files.
|
`README.md` and `package.json` files.
|
||||||
|
|
||||||
| Yarn Commands | Description |
|
| Yarn Commands | Description |
|
||||||
| ---------------------------- | ------------------------------------------------------------- |
|
| ---------------------------- | ------------------------------------------------------------- |
|
||||||
@ -222,15 +225,6 @@ you'll see the following:
|
|||||||
└── README.md # This file
|
└── README.md # This file
|
||||||
```
|
```
|
||||||
|
|
||||||
Want to better understand why and how we've structured this repository? Read
|
|
||||||
more about it in our [Architecture Documentation][ohif-architecture].
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| Name | Description | Links |
|
|
||||||
| ---------------------------------------------------- | ----------------------------------------------------- | ---------------------- |
|
|
||||||
| [@ohif/extension-cornerstone][extension-cornerstone] | 2D image viewing, annotation, and segementation tools | [NPM][cornerstone-npm] |
|
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
To acknowledge the OHIF Viewer in an academic publication, please cite
|
To acknowledge the OHIF Viewer in an academic publication, please cite
|
||||||
@ -259,7 +253,7 @@ or, for v1, please cite:
|
|||||||
> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334)
|
> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334)
|
||||||
|
|
||||||
**Note:** If you use or find this repository helpful, please take the time to
|
**Note:** If you use or find this repository helpful, please take the time to
|
||||||
star this repository on Github. This is an easy way for us to assess adoption
|
star this repository on GitHub. This is an easy way for us to assess adoption
|
||||||
and it can help us obtain future funding for the project.
|
and it can help us obtain future funding for the project.
|
||||||
|
|
||||||
This work is supported primarily by the National Institutes of Health, National
|
This work is supported primarily by the National Institutes of Health, National
|
||||||
@ -267,7 +261,11 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program,
|
|||||||
under a
|
under a
|
||||||
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
|
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
|
||||||
|
|
||||||
This project is tested with BrowserStack. Thank you for supporting open source
|
[NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/) supported the development of new features and bug fixes marked with ["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority),
|
||||||
|
["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate) or ["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration). NCI Imaging Data Commons is supported by contract number 19X037Q from
|
||||||
|
Leidos Biomedical Research under Task Order HHSN26100071 from NCI. [IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized version of the OHIF Viewer.
|
||||||
|
|
||||||
|
This project is tested with BrowserStack. Thank you for supporting open-source!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -3,22 +3,22 @@ const { extendDefaultPlugins } = require('svgo');
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
|
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
|
||||||
presets: [
|
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
|
||||||
'@babel/preset-env',
|
|
||||||
'@babel/preset-react',
|
|
||||||
'@babel/preset-typescript',
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
'inline-react-svg',
|
'inline-react-svg',
|
||||||
{
|
{
|
||||||
svgo: {
|
svgo: {
|
||||||
plugins: extendDefaultPlugins([
|
plugins: [
|
||||||
{
|
{
|
||||||
name: 'removeViewBox',
|
name: 'preset-default',
|
||||||
active: false,
|
params: {
|
||||||
|
overrides: {
|
||||||
|
removeViewBox: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]),
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
1d38fe30a490010c7de487c7a0b1a5bfe3bc75a4
|
157b88c909d3289cb89ace731c1f9a19d40797ac
|
||||||
@ -1,138 +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, viewportIndex, dataSource
|
|
||||||
const getViewportModule = () => {
|
|
||||||
const wrappedViewport = props => {
|
|
||||||
return (
|
|
||||||
<ExampleViewport
|
|
||||||
{...props}
|
|
||||||
onEvent={data => {
|
|
||||||
commandsManager.runCommand('commandName', data);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return [{ name: 'example', component: wrappedViewport }];
|
|
||||||
};
|
|
||||||
@ -3,8 +3,7 @@ const { merge } = require('webpack-merge');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
const pkg = require('./../package.json');
|
const pkg = require('./../package.json');
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
.BundleAnalyzerPlugin;
|
|
||||||
|
|
||||||
const ROOT_DIR = path.join(__dirname, './..');
|
const ROOT_DIR = path.join(__dirname, './..');
|
||||||
const SRC_DIR = path.join(__dirname, '../src');
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
@ -38,13 +37,7 @@ module.exports = (env, argv) => {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
filename: pkg.main,
|
filename: pkg.main,
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
/\b(vtk.js)/,
|
|
||||||
/\b(dcmjs)/,
|
|
||||||
/\b(gl-matrix)/,
|
|
||||||
/^@ohif/,
|
|
||||||
/^@cornerstonejs/,
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
maxChunks: 1,
|
maxChunks: 1,
|
||||||
|
|||||||
441
extensions/cornerstone-dicom-rt/CHANGELOG.md
Normal file
441
extensions/cornerstone-dicom-rt/CHANGELOG.md
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||||
@ -10,7 +10,7 @@ module.exports = {
|
|||||||
modules: 'commonjs',
|
modules: 'commonjs',
|
||||||
debug: false,
|
debug: false,
|
||||||
},
|
},
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
],
|
],
|
||||||
@ -26,7 +26,7 @@ module.exports = {
|
|||||||
// WebPack handles ES6 --> Target Syntax
|
// WebPack handles ES6 --> Target Syntax
|
||||||
['@babel/preset-env', { modules: false }],
|
['@babel/preset-env', { modules: false }],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
},
|
},
|
||||||
@ -35,7 +35,7 @@ module.exports = {
|
|||||||
// WebPack handles ES6 --> Target Syntax
|
// WebPack handles ES6 --> Target Syntax
|
||||||
['@babel/preset-env', { modules: false }],
|
['@babel/preset-env', { modules: false }],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
plugins: ['react-hot-loader/babel'],
|
plugins: ['react-hot-loader/babel'],
|
||||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||||
"version": "3.6.0",
|
"version": "3.7.0-beta.110",
|
||||||
"description": "DICOM RT read workflow",
|
"description": "DICOM RT read workflow",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -31,10 +31,10 @@
|
|||||||
"start": "yarn run dev"
|
"start": "yarn run dev"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "3.6.0",
|
"@ohif/core": "3.7.0-beta.110",
|
||||||
"@ohif/extension-cornerstone": "3.6.0",
|
"@ohif/extension-cornerstone": "3.7.0-beta.110",
|
||||||
"@ohif/extension-default": "3.6.0",
|
"@ohif/extension-default": "3.7.0-beta.110",
|
||||||
"@ohif/i18n": "3.6.0",
|
"@ohif/i18n": "3.7.0-beta.110",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
|||||||
@ -7,11 +7,7 @@ const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3'];
|
|||||||
|
|
||||||
let loadPromises = {};
|
let loadPromises = {};
|
||||||
|
|
||||||
function _getDisplaySetsFromSeries(
|
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
) {
|
|
||||||
const instance = instances[0];
|
const instance = instances[0];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -56,10 +52,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
if (
|
if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) {
|
||||||
instance.ReferencedFrameOfReferenceSequence &&
|
|
||||||
!instance.ReferencedSeriesSequence
|
|
||||||
) {
|
|
||||||
instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
||||||
instance.ReferencedFrameOfReferenceSequence
|
instance.ReferencedFrameOfReferenceSequence
|
||||||
);
|
);
|
||||||
@ -72,8 +65,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
|
|
||||||
const referencedSeries = referencedSeriesSequence[0];
|
const referencedSeries = referencedSeriesSequence[0];
|
||||||
|
|
||||||
displaySet.referencedImages =
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||||
instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
|
||||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||||
|
|
||||||
displaySet.getReferenceDisplaySet = () => {
|
displaySet.getReferenceDisplaySet = () => {
|
||||||
@ -88,14 +80,12 @@ function _getDisplaySetsFromSeries(
|
|||||||
|
|
||||||
const referencedDisplaySet = referencedDisplaySets[0];
|
const referencedDisplaySet = referencedDisplaySets[0];
|
||||||
|
|
||||||
displaySet.referencedDisplaySetInstanceUID =
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||||
referencedDisplaySet.displaySetInstanceUID;
|
|
||||||
|
|
||||||
return referencedDisplaySet;
|
return referencedDisplaySet;
|
||||||
};
|
};
|
||||||
|
|
||||||
displaySet.load = ({ headers }) =>
|
displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers);
|
||||||
_load(displaySet, servicesManager, extensionManager, headers);
|
|
||||||
|
|
||||||
return [displaySet];
|
return [displaySet];
|
||||||
}
|
}
|
||||||
@ -194,11 +184,7 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
|||||||
name: 'dicom-rt',
|
name: 'dicom-rt',
|
||||||
sopClassUids,
|
sopClassUids,
|
||||||
getDisplaySetsFromSeries: instances => {
|
getDisplaySetsFromSeries: instances => {
|
||||||
return _getDisplaySetsFromSeries(
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -4,9 +4,7 @@ import { Types } from '@ohif/core';
|
|||||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport');
|
||||||
/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const OHIFCornerstoneRTViewport = props => {
|
const OHIFCornerstoneRTViewport = props => {
|
||||||
@ -36,12 +34,14 @@ const extension: Types.Extensions.Extension = {
|
|||||||
getViewportModule({
|
getViewportModule({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
extensionManager,
|
extensionManager,
|
||||||
|
commandsManager,
|
||||||
}: Types.Extensions.ExtensionParams) {
|
}: Types.Extensions.ExtensionParams) {
|
||||||
const ExtendedOHIFCornerstoneRTViewport = props => {
|
const ExtendedOHIFCornerstoneRTViewport = props => {
|
||||||
return (
|
return (
|
||||||
<OHIFCornerstoneRTViewport
|
<OHIFCornerstoneRTViewport
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
extensionManager={extensionManager}
|
extensionManager={extensionManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,23 +25,13 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
|
|
||||||
if (Array.isArray(contourData)) {
|
if (Array.isArray(contourData)) {
|
||||||
promisesMap.has(referencedROINumber)
|
promisesMap.has(referencedROINumber)
|
||||||
? promisesMap
|
? promisesMap.get(referencedROINumber).push(Promise.resolve(contourData))
|
||||||
.get(referencedROINumber)
|
: promisesMap.set(referencedROINumber, [Promise.resolve(contourData)]);
|
||||||
.push(Promise.resolve(contourData))
|
|
||||||
: promisesMap.set(referencedROINumber, [
|
|
||||||
Promise.resolve(contourData),
|
|
||||||
]);
|
|
||||||
} else if (contourData && contourData.BulkDataURI) {
|
} else if (contourData && contourData.BulkDataURI) {
|
||||||
const bulkDataURI = contourData.BulkDataURI;
|
const bulkDataURI = contourData.BulkDataURI;
|
||||||
|
|
||||||
if (
|
if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) {
|
||||||
!datasource ||
|
return Promise.reject('Invalid datasource object or retrieve function');
|
||||||
!datasource.retrieve ||
|
|
||||||
!datasource.retrieve.bulkDataURI
|
|
||||||
) {
|
|
||||||
return Promise.reject(
|
|
||||||
'Invalid datasource object or retrieve function'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bulkDataPromise = datasource.retrieve.bulkDataURI({
|
const bulkDataPromise = datasource.retrieve.bulkDataURI({
|
||||||
@ -74,10 +64,7 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
ROIContour.ContourSequence.forEach((Contour, index) => {
|
ROIContour.ContourSequence.forEach((Contour, index) => {
|
||||||
const promise = resolvedPromises[index];
|
const promise = resolvedPromises[index];
|
||||||
if (promise.status === 'fulfilled') {
|
if (promise.status === 'fulfilled') {
|
||||||
if (
|
if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) {
|
||||||
Array.isArray(promise.value) &&
|
|
||||||
promise.value.every(Number.isFinite)
|
|
||||||
) {
|
|
||||||
// If promise.value is already an array of numbers, use it directly
|
// If promise.value is already an array of numbers, use it directly
|
||||||
Contour.ContourData = promise.value;
|
Contour.ContourData = promise.value;
|
||||||
} else {
|
} else {
|
||||||
@ -85,13 +72,8 @@ async function checkAndLoadContourData(instance, datasource) {
|
|||||||
const uint8Array = new Uint8Array(promise.value);
|
const uint8Array = new Uint8Array(promise.value);
|
||||||
const textDecoder = new TextDecoder();
|
const textDecoder = new TextDecoder();
|
||||||
const dataUint8Array = textDecoder.decode(uint8Array);
|
const dataUint8Array = textDecoder.decode(uint8Array);
|
||||||
if (
|
if (typeof dataUint8Array === 'string' && dataUint8Array.includes('\\')) {
|
||||||
typeof dataUint8Array === 'string' &&
|
Contour.ContourData = dataUint8Array.split('\\').map(parseFloat);
|
||||||
dataUint8Array.includes('\\')
|
|
||||||
) {
|
|
||||||
Contour.ContourData = dataUint8Array
|
|
||||||
.split('\\')
|
|
||||||
.map(parseFloat);
|
|
||||||
} else {
|
} else {
|
||||||
Contour.ContourData = [];
|
Contour.ContourData = [];
|
||||||
}
|
}
|
||||||
@ -120,9 +102,8 @@ export default async function loadRTStruct(
|
|||||||
const { bulkDataURI } = dataSource.getConfig?.() || {};
|
const { bulkDataURI } = dataSource.getConfig?.() || {};
|
||||||
|
|
||||||
const { dicomLoaderService } = utilityModule.exports;
|
const { dicomLoaderService } = utilityModule.exports;
|
||||||
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
|
const imageIdSopInstanceUidPairs =
|
||||||
referencedDisplaySet
|
_getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet);
|
||||||
);
|
|
||||||
|
|
||||||
// Set here is loading is asynchronous.
|
// Set here is loading is asynchronous.
|
||||||
// If this function throws its set back to false.
|
// If this function throws its set back to false.
|
||||||
@ -137,20 +118,14 @@ export default async function loadRTStruct(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
||||||
const rtStructDataset = DicomMetaDictionary.naturalizeDataset(
|
const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||||
dicomData.dict
|
|
||||||
);
|
|
||||||
rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||||
instance = rtStructDataset;
|
instance = rtStructDataset;
|
||||||
} else {
|
} else {
|
||||||
await checkAndLoadContourData(instance, dataSource);
|
await checkAndLoadContourData(instance, dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance;
|
||||||
StructureSetROISequence,
|
|
||||||
ROIContourSequence,
|
|
||||||
RTROIObservationsSequence,
|
|
||||||
} = instance;
|
|
||||||
|
|
||||||
// Define our structure set entry and add it to the rtstruct module state.
|
// Define our structure set entry and add it to the rtstruct module state.
|
||||||
const structureSet = {
|
const structureSet = {
|
||||||
@ -174,19 +149,9 @@ export default async function loadRTStruct(
|
|||||||
|
|
||||||
const contourPoints = [];
|
const contourPoints = [];
|
||||||
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
||||||
const {
|
const { ContourImageSequence, ContourData, NumberOfContourPoints, ContourGeometricType } =
|
||||||
ContourImageSequence,
|
ContourSequenceArray[c];
|
||||||
ContourData,
|
|
||||||
NumberOfContourPoints,
|
|
||||||
ContourGeometricType,
|
|
||||||
} = ContourSequenceArray[c];
|
|
||||||
|
|
||||||
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
|
||||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
|
||||||
|
|
||||||
if (!imageId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let isSupported = false;
|
let isSupported = false;
|
||||||
|
|
||||||
const points = [];
|
const points = [];
|
||||||
@ -235,9 +200,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
|||||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return imageIdSopInstanceUidPairsEntry
|
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
||||||
? imageIdSopInstanceUidPairsEntry.imageId
|
|
||||||
: null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) {
|
function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) {
|
||||||
@ -258,8 +221,7 @@ function _setROIContourMetadata(
|
|||||||
isSupported
|
isSupported
|
||||||
) {
|
) {
|
||||||
const StructureSetROI = StructureSetROISequence.find(
|
const StructureSetROI = StructureSetROISequence.find(
|
||||||
structureSetROI =>
|
structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber
|
||||||
structureSetROI.ROINumber === ROIContour.ReferencedROINumber
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ROIContourData = {
|
const ROIContourData = {
|
||||||
@ -299,23 +261,15 @@ function _setROIContourDataColor(ROIContour, ROIContourData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _setROIContourRTROIObservations(
|
function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequence, ROINumber) {
|
||||||
ROIContourData,
|
|
||||||
RTROIObservationsSequence,
|
|
||||||
ROINumber
|
|
||||||
) {
|
|
||||||
const RTROIObservations = RTROIObservationsSequence.find(
|
const RTROIObservations = RTROIObservationsSequence.find(
|
||||||
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
|
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
|
||||||
);
|
);
|
||||||
|
|
||||||
if (RTROIObservations) {
|
if (RTROIObservations) {
|
||||||
// Deep copy so we don't keep the reference to the dcmjs dataset entry.
|
// Deep copy so we don't keep the reference to the dcmjs dataset entry.
|
||||||
const {
|
const { ObservationNumber, ROIObservationDescription, RTROIInterpretedType, ROIInterpreter } =
|
||||||
ObservationNumber,
|
RTROIObservations;
|
||||||
ROIObservationDescription,
|
|
||||||
RTROIInterpretedType,
|
|
||||||
ROIInterpreter,
|
|
||||||
} = RTROIObservations;
|
|
||||||
|
|
||||||
ROIContourData.RTROIObservations = {
|
ROIContourData.RTROIObservations = {
|
||||||
ObservationNumber,
|
ObservationNumber,
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
async function _hydrateRTDisplaySet({
|
|
||||||
rtDisplaySet,
|
|
||||||
viewportIndex,
|
|
||||||
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(
|
|
||||||
viewportIndex,
|
|
||||||
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, index) => {
|
|
||||||
if (index === viewportIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
|
|
||||||
viewport.displaySetInstanceUIDs,
|
|
||||||
rtDisplaySet.displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldDisplaySeg) {
|
|
||||||
updatedViewports.push({
|
|
||||||
viewportIndex: index,
|
|
||||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
|
||||||
viewportOptions: {
|
|
||||||
initialImageOptions: {
|
|
||||||
preset: 'middle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do the entire update at once
|
|
||||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default _hydrateRTDisplaySet;
|
|
||||||
@ -1,12 +1,7 @@
|
|||||||
function createRTToolGroupAndAddTools(
|
function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
|
||||||
ToolGroupService,
|
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
||||||
customizationService,
|
|
||||||
toolGroupId
|
|
||||||
) {
|
|
||||||
const { tools } =
|
|
||||||
customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
|
||||||
|
|
||||||
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {});
|
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createRTToolGroupAndAddTools;
|
export default createRTToolGroupAndAddTools;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import hydrateRTDisplaySet from './_hydrateRT';
|
import { ButtonEnums } from '@ohif/ui';
|
||||||
|
|
||||||
const RESPONSE = {
|
const RESPONSE = {
|
||||||
NO_NEVER: -1,
|
NO_NEVER: -1,
|
||||||
@ -9,21 +9,24 @@ const RESPONSE = {
|
|||||||
function promptHydrateRT({
|
function promptHydrateRT({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
rtDisplaySet,
|
rtDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
toolGroupId = 'default',
|
toolGroupId = 'default',
|
||||||
|
preHydrateCallbacks,
|
||||||
|
hydrateRTDisplaySet,
|
||||||
}) {
|
}) {
|
||||||
const { uiViewportDialogService } = servicesManager.services;
|
const { uiViewportDialogService } = servicesManager.services;
|
||||||
|
|
||||||
return new Promise(async function(resolve, reject) {
|
return new Promise(async function (resolve, reject) {
|
||||||
const promptResult = await _askHydrate(
|
const promptResult = await _askHydrate(uiViewportDialogService, viewportId);
|
||||||
uiViewportDialogService,
|
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||||
|
preHydrateCallbacks?.forEach(callback => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
const isHydrated = await hydrateRTDisplaySet({
|
const isHydrated = await hydrateRTDisplaySet({
|
||||||
rtDisplaySet,
|
rtDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
toolGroupId,
|
toolGroupId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
});
|
});
|
||||||
@ -33,17 +36,17 @@ function promptHydrateRT({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _askHydrate(uiViewportDialogService, viewportIndex) {
|
function _askHydrate(uiViewportDialogService, viewportId) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
const message = 'Do you want to open this Segmentation?';
|
const message = 'Do you want to open this Segmentation?';
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: ButtonEnums.type.secondary,
|
||||||
text: 'No',
|
text: 'No',
|
||||||
value: RESPONSE.CANCEL,
|
value: RESPONSE.CANCEL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'primary',
|
type: ButtonEnums.type.primary,
|
||||||
text: 'Yes',
|
text: 'Yes',
|
||||||
value: RESPONSE.HYDRATE_SEG,
|
value: RESPONSE.HYDRATE_SEG,
|
||||||
},
|
},
|
||||||
@ -54,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
uiViewportDialogService.show({
|
uiViewportDialogService.show({
|
||||||
viewportIndex,
|
viewportId,
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message,
|
message,
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@ -1,17 +1,11 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import OHIF, { utils } from '@ohif/core';
|
import OHIF, { utils } from '@ohif/core';
|
||||||
import {
|
import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
|
||||||
ViewportActionBar,
|
|
||||||
useViewportGrid,
|
|
||||||
LoadingIndicatorTotalPercent,
|
|
||||||
} from '@ohif/ui';
|
|
||||||
|
|
||||||
import _hydrateRTdisplaySet from '../utils/_hydrateRT';
|
|
||||||
import promptHydrateRT from '../utils/promptHydrateRT';
|
import promptHydrateRT from '../utils/promptHydrateRT';
|
||||||
import _getStatusComponent from './_getStatusComponent';
|
import _getStatusComponent from './_getStatusComponent';
|
||||||
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
||||||
import _hydrateRTDisplaySet from '../utils/_hydrateRT';
|
|
||||||
|
|
||||||
const { formatDate } = utils;
|
const { formatDate } = utils;
|
||||||
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
||||||
@ -21,10 +15,10 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
children,
|
children,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
viewportIndex,
|
|
||||||
viewportLabel,
|
viewportLabel,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
extensionManager,
|
extensionManager,
|
||||||
|
commandsManager,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -35,7 +29,9 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
customizationService,
|
customizationService,
|
||||||
} = servicesManager.services;
|
} = servicesManager.services;
|
||||||
|
|
||||||
const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`;
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
|
||||||
|
const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`;
|
||||||
|
|
||||||
// RT viewport will always have a single display set
|
// RT viewport will always have a single display set
|
||||||
if (displaySets.length > 1) {
|
if (displaySets.length > 1) {
|
||||||
@ -66,12 +62,10 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
// refs
|
// refs
|
||||||
const referencedDisplaySetRef = useRef(null);
|
const referencedDisplaySetRef = useRef(null);
|
||||||
|
|
||||||
const { viewports, activeViewportIndex } = viewportGrid;
|
const { viewports, activeViewportId } = viewportGrid;
|
||||||
|
|
||||||
const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet();
|
const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet();
|
||||||
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet);
|
||||||
referencedDisplaySet
|
|
||||||
);
|
|
||||||
|
|
||||||
referencedDisplaySetRef.current = {
|
referencedDisplaySetRef.current = {
|
||||||
displaySet: referencedDisplaySet,
|
displaySet: referencedDisplaySet,
|
||||||
@ -91,14 +85,27 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
setElement(null);
|
setElement(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storePresentationState = useCallback(() => {
|
||||||
|
viewportGrid?.viewports.forEach(({ viewportId }) => {
|
||||||
|
commandsManager.runCommand('storePresentation', {
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [viewportGrid]);
|
||||||
|
|
||||||
|
const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => {
|
||||||
|
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||||
|
displaySets: [rtDisplaySet],
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getCornerstoneViewport = useCallback(() => {
|
const getCornerstoneViewport = useCallback(() => {
|
||||||
const { component: Component } = extensionManager.getModuleEntry(
|
const { component: Component } = extensionManager.getModuleEntry(
|
||||||
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||||
displaySet: referencedDisplaySet,
|
|
||||||
} = referencedDisplaySetRef.current;
|
|
||||||
|
|
||||||
// Todo: jump to the center of the first segment
|
// Todo: jump to the center of the first segment
|
||||||
return (
|
return (
|
||||||
@ -115,7 +122,7 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
onElementDisabled={onElementDisabled}
|
onElementDisabled={onElementDisabled}
|
||||||
></Component>
|
></Component>
|
||||||
);
|
);
|
||||||
}, [viewportIndex, rtDisplaySet, toolGroupId]);
|
}, [viewportId, rtDisplaySet, toolGroupId]);
|
||||||
|
|
||||||
const onSegmentChange = useCallback(
|
const onSegmentChange = useCallback(
|
||||||
direction => {
|
direction => {
|
||||||
@ -136,11 +143,7 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
newSelectedSegmentIndex = numberOfSegments - 1;
|
newSelectedSegmentIndex = numberOfSegments - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentationService.jumpToSegmentCenter(
|
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId);
|
||||||
segmentationId,
|
|
||||||
newSelectedSegmentIndex,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
setSelectedSegment(newSelectedSegmentIndex);
|
setSelectedSegment(newSelectedSegmentIndex);
|
||||||
},
|
},
|
||||||
[selectedSegment]
|
[selectedSegment]
|
||||||
@ -153,31 +156,29 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
|
|
||||||
promptHydrateRT({
|
promptHydrateRT({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
rtDisplaySet,
|
rtDisplaySet,
|
||||||
|
preHydrateCallbacks: [storePresentationState],
|
||||||
|
hydrateRTDisplaySet,
|
||||||
}).then(isHydrated => {
|
}).then(isHydrated => {
|
||||||
if (isHydrated) {
|
if (isHydrated) {
|
||||||
setIsHydrated(true);
|
setIsHydrated(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]);
|
}, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = segmentationService.subscribe(
|
const { unsubscribe } = segmentationService.subscribe(
|
||||||
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||||
evt => {
|
evt => {
|
||||||
if (
|
if (evt.rtDisplaySet.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
|
||||||
evt.rtDisplaySet.displaySetInstanceUID ===
|
|
||||||
rtDisplaySet.displaySetInstanceUID
|
|
||||||
) {
|
|
||||||
setRtIsLoading(false);
|
setRtIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.overlappingSegments) {
|
if (evt.overlappingSegments) {
|
||||||
uiNotificationService.show({
|
uiNotificationService.show({
|
||||||
title: 'Overlapping Segments',
|
title: 'Overlapping Segments',
|
||||||
message:
|
message: 'Overlapping segments detected which is not currently supported',
|
||||||
'Overlapping segments detected which is not currently supported',
|
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -212,12 +213,10 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||||
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||||
({ displaySetInstanceUIDs }) => {
|
({ displaySetInstanceUIDs }) => {
|
||||||
const activeViewport = viewports[activeViewportIndex];
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
if (
|
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||||
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
|
|
||||||
) {
|
|
||||||
viewportGridService.setDisplaySetsForViewport({
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
viewportIndex: activeViewportIndex,
|
viewportId: activeViewportId,
|
||||||
displaySetInstanceUIDs: [],
|
displaySetInstanceUIDs: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -236,19 +235,13 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toolGroup = createRTToolGroupAndAddTools(
|
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||||
toolGroupService,
|
|
||||||
customizationService,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
|
|
||||||
setToolGroupCreated(true);
|
setToolGroupCreated(true);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove the segmentation representations if seg displayset changed
|
// remove the segmentation representations if seg displayset changed
|
||||||
segmentationService.removeSegmentationRepresentationFromToolGroup(
|
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
|
|
||||||
toolGroupService.destroyToolGroup(toolGroupId);
|
toolGroupService.destroyToolGroup(toolGroupId);
|
||||||
};
|
};
|
||||||
@ -259,9 +252,7 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove the segmentation representations if seg displayset changed
|
// remove the segmentation representations if seg displayset changed
|
||||||
segmentationService.removeSegmentationRepresentationFromToolGroup(
|
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
referencedDisplaySetRef.current = null;
|
referencedDisplaySetRef.current = null;
|
||||||
};
|
};
|
||||||
}, [rtDisplaySet]);
|
}, [rtDisplaySet]);
|
||||||
@ -282,7 +273,7 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
return (
|
return (
|
||||||
child &&
|
child &&
|
||||||
React.cloneElement(child, {
|
React.cloneElement(child, {
|
||||||
viewportIndex,
|
viewportId,
|
||||||
key: index,
|
key: index,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -303,10 +294,16 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
} = referencedDisplaySetRef.current.metadata;
|
} = referencedDisplaySetRef.current.metadata;
|
||||||
|
|
||||||
const onStatusClick = async () => {
|
const onStatusClick = async () => {
|
||||||
const isHydrated = await _hydrateRTDisplaySet({
|
// Before hydrating a RT and make it added to all viewports in the grid
|
||||||
|
// that share the same frameOfReferenceUID, we need to store the viewport grid
|
||||||
|
// presentation state, so that we can restore it after hydrating the RT. This is
|
||||||
|
// required if the user has changed the viewport (other viewport than RT viewport)
|
||||||
|
// 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({
|
||||||
rtDisplaySet,
|
rtDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsHydrated(isHydrated);
|
setIsHydrated(isHydrated);
|
||||||
@ -333,26 +330,22 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
currentSeries: SeriesNumber,
|
currentSeries: SeriesNumber,
|
||||||
seriesDescription: `RT Viewport ${SeriesDescription}`,
|
seriesDescription: `RT Viewport ${SeriesDescription}`,
|
||||||
patientInformation: {
|
patientInformation: {
|
||||||
patientName: PatientName
|
patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '',
|
||||||
? OHIF.utils.formatPN(PatientName.Alphabetic)
|
|
||||||
: '',
|
|
||||||
patientSex: PatientSex || '',
|
patientSex: PatientSex || '',
|
||||||
patientAge: PatientAge || '',
|
patientAge: PatientAge || '',
|
||||||
MRN: PatientID || '',
|
MRN: PatientID || '',
|
||||||
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
|
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
|
||||||
spacing:
|
spacing:
|
||||||
SpacingBetweenSlices !== undefined
|
SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '',
|
||||||
? `${SpacingBetweenSlices.toFixed(2)}mm`
|
|
||||||
: '',
|
|
||||||
scanner: ManufacturerModelName || '',
|
scanner: ManufacturerModelName || '',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative flex flex-row w-full h-full overflow-hidden">
|
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||||
{rtIsLoading && (
|
{rtIsLoading && (
|
||||||
<LoadingIndicatorTotalPercent
|
<LoadingIndicatorTotalPercent
|
||||||
className="w-full h-full"
|
className="h-full w-full"
|
||||||
totalNumbers={processingProgress.totalSegments}
|
totalNumbers={processingProgress.totalSegments}
|
||||||
percentComplete={processingProgress.percentComplete}
|
percentComplete={processingProgress.percentComplete}
|
||||||
loadingText="Loading RTSTRUCT..."
|
loadingText="Loading RTSTRUCT..."
|
||||||
@ -367,7 +360,7 @@ function OHIFCornerstoneRTViewport(props) {
|
|||||||
|
|
||||||
OHIFCornerstoneRTViewport.propTypes = {
|
OHIFCornerstoneRTViewport.propTypes = {
|
||||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
viewportIndex: PropTypes.number.isRequired,
|
viewportId: PropTypes.string.isRequired,
|
||||||
dataSource: PropTypes.object,
|
dataSource: PropTypes.object,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
customProps: PropTypes.object,
|
customProps: PropTypes.object,
|
||||||
|
|||||||
@ -13,25 +13,28 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
|||||||
case true:
|
case true:
|
||||||
StatusIcon = () => <Icon name="status-alert" />;
|
StatusIcon = () => <Icon name="status-alert" />;
|
||||||
|
|
||||||
ToolTipMessage = () => (
|
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
|
||||||
<div>This Segmentation is loaded in the segmentation panel</div>
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case false:
|
case false:
|
||||||
StatusIcon = () => <Icon name="status-untracked" />;
|
StatusIcon = () => (
|
||||||
|
<Icon
|
||||||
|
className="text-aqua-pale"
|
||||||
|
name="status-untracked"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>;
|
ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusArea = () => (
|
const StatusArea = () => (
|
||||||
<div className="flex h-6 leading-6 cursor-default text-sm text-white">
|
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
||||||
<div className="min-w-[45px] flex items-center p-1 rounded-l-xl rounded-r bg-customgray-100">
|
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
||||||
<StatusIcon />
|
<StatusIcon />
|
||||||
<span className="ml-1">RTSTRUCT</span>
|
<span className="ml-1">RTSTRUCT</span>
|
||||||
</div>
|
</div>
|
||||||
{!isHydrated && (
|
{!isHydrated && (
|
||||||
<div
|
<div
|
||||||
className="ml-1 px-1.5 rounded cursor-pointer hover:text-black bg-primary-main hover:bg-primary-light"
|
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
||||||
onMouseUp={onStatusClick}
|
onMouseUp={onStatusClick}
|
||||||
>
|
>
|
||||||
@ -44,7 +47,10 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ToolTipMessage && (
|
{ToolTipMessage && (
|
||||||
<Tooltip content={<ToolTipMessage />} position="bottom-left">
|
<Tooltip
|
||||||
|
content={<ToolTipMessage />}
|
||||||
|
position="bottom-left"
|
||||||
|
>
|
||||||
<StatusArea />
|
<StatusArea />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -40,21 +40,15 @@ module.exports = (env, argv) => {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
filename: pkg.main,
|
filename: pkg.main,
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
/\b(vtk.js)/,
|
|
||||||
/\b(dcmjs)/,
|
|
||||||
/\b(gl-matrix)/,
|
|
||||||
/^@ohif/,
|
|
||||||
/^@cornerstonejs/,
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
maxChunks: 1,
|
maxChunks: 1,
|
||||||
}),
|
}),
|
||||||
// new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
// filename: `./dist/${outputName}.css`,
|
filename: `./dist/${outputName}.css`,
|
||||||
// chunkFilename: `./dist/${outputName}.css`,
|
chunkFilename: `./dist/${outputName}.css`,
|
||||||
// }),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
460
extensions/cornerstone-dicom-seg/CHANGELOG.md
Normal file
460
extensions/cornerstone-dicom-seg/CHANGELOG.md
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||||
@ -10,7 +10,7 @@ module.exports = {
|
|||||||
modules: 'commonjs',
|
modules: 'commonjs',
|
||||||
debug: false,
|
debug: false,
|
||||||
},
|
},
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
],
|
],
|
||||||
@ -26,7 +26,7 @@ module.exports = {
|
|||||||
// WebPack handles ES6 --> Target Syntax
|
// WebPack handles ES6 --> Target Syntax
|
||||||
['@babel/preset-env', { modules: false }],
|
['@babel/preset-env', { modules: false }],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
},
|
},
|
||||||
@ -35,7 +35,7 @@ module.exports = {
|
|||||||
// WebPack handles ES6 --> Target Syntax
|
// WebPack handles ES6 --> Target Syntax
|
||||||
['@babel/preset-env', { modules: false }],
|
['@babel/preset-env', { modules: false }],
|
||||||
'@babel/preset-react',
|
'@babel/preset-react',
|
||||||
"@babel/preset-typescript",
|
'@babel/preset-typescript',
|
||||||
],
|
],
|
||||||
plugins: ['react-hot-loader/babel'],
|
plugins: ['react-hot-loader/babel'],
|
||||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||||
"version": "3.6.0",
|
"version": "3.7.0-beta.110",
|
||||||
"description": "DICOM SEG read workflow",
|
"description": "DICOM SEG read workflow",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -31,10 +31,10 @@
|
|||||||
"start": "yarn run dev"
|
"start": "yarn run dev"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "3.6.0",
|
"@ohif/core": "3.7.0-beta.110",
|
||||||
"@ohif/extension-cornerstone": "3.6.0",
|
"@ohif/extension-cornerstone": "3.7.0-beta.110",
|
||||||
"@ohif/extension-default": "3.6.0",
|
"@ohif/extension-default": "3.7.0-beta.110",
|
||||||
"@ohif/i18n": "3.6.0",
|
"@ohif/i18n": "3.7.0-beta.110",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@ -44,6 +44,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@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"
|
"react-color": "^2.19.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
435
extensions/cornerstone-dicom-seg/src/commandsModule.ts
Normal file
435
extensions/cornerstone-dicom-seg/src/commandsModule.ts
Normal file
@ -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;
|
||||||
@ -5,7 +5,6 @@ const segProtocol: Types.HangingProtocol.Protocol = {
|
|||||||
// Don't store this hanging protocol as it applies to the currently active
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
// display set by default
|
// display set by default
|
||||||
// cacheId: null,
|
// cacheId: null,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'Segmentations',
|
name: 'Segmentations',
|
||||||
// Just apply this one when specifically listed
|
// Just apply this one when specifically listed
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
|
|||||||
70
extensions/cornerstone-dicom-seg/src/getPanelModule.tsx
Normal file
70
extensions/cornerstone-dicom-seg/src/getPanelModule.tsx
Normal file
@ -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;
|
||||||
@ -1,21 +1,15 @@
|
|||||||
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
|
|
||||||
|
|
||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
|
import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||||
|
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
|
||||||
|
|
||||||
import { SOPClassHandlerId } from './id';
|
import { SOPClassHandlerId } from './id';
|
||||||
import dcmjs from 'dcmjs';
|
import { dicomlabToRGB } from './utils/dicomlabToRGB';
|
||||||
|
|
||||||
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
|
|
||||||
|
|
||||||
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4'];
|
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4'];
|
||||||
|
|
||||||
let loadPromises = {};
|
let loadPromises = {};
|
||||||
|
|
||||||
function _getDisplaySetsFromSeries(
|
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
) {
|
|
||||||
const instance = instances[0];
|
const instance = instances[0];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -63,13 +57,13 @@ function _getDisplaySetsFromSeries(
|
|||||||
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
|
|
||||||
if (!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 =
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||||
instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
|
||||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||||
|
|
||||||
displaySet.getReferenceDisplaySet = () => {
|
displaySet.getReferenceDisplaySet = () => {
|
||||||
@ -84,8 +78,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
|
|
||||||
const referencedDisplaySet = referencedDisplaySets[0];
|
const referencedDisplaySet = referencedDisplaySets[0];
|
||||||
|
|
||||||
displaySet.referencedDisplaySetInstanceUID =
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||||
referencedDisplaySet.displaySetInstanceUID;
|
|
||||||
|
|
||||||
// Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc.
|
// Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc.
|
||||||
displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
||||||
@ -118,17 +111,13 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) {
|
|||||||
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
||||||
// and also return the same promise to any other callers.
|
// and also return the same promise to any other callers.
|
||||||
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
||||||
if (
|
if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) {
|
||||||
!segDisplaySet.segments ||
|
await _loadSegments({
|
||||||
Object.keys(segDisplaySet.segments).length === 0
|
|
||||||
) {
|
|
||||||
const segments = await _loadSegments(
|
|
||||||
extensionManager,
|
extensionManager,
|
||||||
|
servicesManager,
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
headers
|
headers,
|
||||||
);
|
});
|
||||||
|
|
||||||
segDisplaySet.segments = segments;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const suppressEvents = true;
|
const suppressEvents = true;
|
||||||
@ -147,152 +136,61 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) {
|
|||||||
return loadPromises[SOPInstanceUID];
|
return loadPromises[SOPInstanceUID];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _loadSegments(extensionManager, segDisplaySet, headers) {
|
async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers }) {
|
||||||
const utilityModule = extensionManager.getModuleEntry(
|
const utilityModule = extensionManager.getModuleEntry(
|
||||||
'@ohif/extension-cornerstone.utilityModule.common'
|
'@ohif/extension-cornerstone.utilityModule.common'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { segmentationService } = servicesManager.services;
|
||||||
|
|
||||||
const { dicomLoaderService } = utilityModule.exports;
|
const { dicomLoaderService } = utilityModule.exports;
|
||||||
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
|
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);
|
||||||
segDisplaySet,
|
|
||||||
null,
|
|
||||||
headers
|
|
||||||
);
|
|
||||||
|
|
||||||
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId);
|
||||||
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
|
||||||
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
|
||||||
|
|
||||||
if (!Array.isArray(dataset.SegmentSequence)) {
|
if (!cachedReferencedVolume) {
|
||||||
dataset.SegmentSequence = [dataset.SegmentSequence];
|
throw new Error(
|
||||||
|
'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const segments = _getSegments(dataset);
|
const { imageIds } = cachedReferencedVolume;
|
||||||
return segments;
|
|
||||||
|
// Todo: what should be defaults here
|
||||||
|
const tolerance = 0.001;
|
||||||
|
const skipOverlapping = true;
|
||||||
|
|
||||||
|
eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => {
|
||||||
|
const { percentComplete } = evt.detail;
|
||||||
|
segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, {
|
||||||
|
percentComplete,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState(
|
||||||
|
imageIds,
|
||||||
|
arrayBuffer,
|
||||||
|
metaData,
|
||||||
|
{ skipOverlapping, tolerance, eventTarget, triggerEvent }
|
||||||
|
);
|
||||||
|
|
||||||
|
results.segMetadata.data.forEach((data, i) => {
|
||||||
|
if (i > 0) {
|
||||||
|
data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(segDisplaySet, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _segmentationExists(segDisplaySet, segmentationService) {
|
function _segmentationExists(segDisplaySet, segmentationService) {
|
||||||
// This should be abstracted with the CornerstoneCacheService
|
// This should be abstracted with the CornerstoneCacheService
|
||||||
return segmentationService.getSegmentation(
|
return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
||||||
segDisplaySet.displaySetInstanceUID
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getPixelData(dataset, segments) {
|
|
||||||
let frameSize = Math.ceil((dataset.Rows * dataset.Columns) / 8);
|
|
||||||
let nextOffset = 0;
|
|
||||||
|
|
||||||
Object.keys(segments).forEach(segmentKey => {
|
|
||||||
const segment = segments[segmentKey];
|
|
||||||
segment.numberOfFrames = segment.functionalGroups.length;
|
|
||||||
segment.size = segment.numberOfFrames * frameSize;
|
|
||||||
segment.offset = nextOffset;
|
|
||||||
nextOffset = segment.offset + segment.size;
|
|
||||||
const packedSegment = dataset.PixelData[0].slice(
|
|
||||||
segment.offset,
|
|
||||||
nextOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
segment.pixelData = dcmjs.data.BitArray.unpack(packedSegment);
|
|
||||||
segment.geometry = geometryFromFunctionalGroups(
|
|
||||||
dataset,
|
|
||||||
segment.functionalGroups
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
function geometryFromFunctionalGroups(dataset, perFrame) {
|
|
||||||
let pixelMeasures =
|
|
||||||
dataset.SharedFunctionalGroupsSequence.PixelMeasuresSequence;
|
|
||||||
let planeOrientation =
|
|
||||||
dataset.SharedFunctionalGroupsSequence.PlaneOrientationSequence;
|
|
||||||
let planePosition = perFrame[0].PlanePositionSequence; // TODO: assume sorted frames!
|
|
||||||
|
|
||||||
const geometry = {};
|
|
||||||
|
|
||||||
// NB: DICOM PixelSpacing is defined as Row then Column,
|
|
||||||
// unlike ImageOrientationPatient
|
|
||||||
let spacingBetweenSlices = pixelMeasures.SpacingBetweenSlices;
|
|
||||||
if (!spacingBetweenSlices) {
|
|
||||||
if (pixelMeasures.SliceThickness) {
|
|
||||||
console.log('Using SliceThickness as SpacingBetweenSlices');
|
|
||||||
spacingBetweenSlices = pixelMeasures.SliceThickness;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geometry.spacing = [
|
|
||||||
pixelMeasures.PixelSpacing[1],
|
|
||||||
pixelMeasures.PixelSpacing[0],
|
|
||||||
spacingBetweenSlices,
|
|
||||||
].map(Number);
|
|
||||||
|
|
||||||
geometry.dimensions = [dataset.Columns, dataset.Rows, perFrame.length].map(
|
|
||||||
Number
|
|
||||||
);
|
|
||||||
|
|
||||||
let orientation = planeOrientation.ImageOrientationPatient.map(Number);
|
|
||||||
const columnStepToPatient = orientation.slice(0, 3);
|
|
||||||
const rowStepToPatient = orientation.slice(3, 6);
|
|
||||||
geometry.planeNormal = [];
|
|
||||||
vtkMath.cross(columnStepToPatient, rowStepToPatient, geometry.planeNormal);
|
|
||||||
|
|
||||||
let firstPosition = perFrame[0].PlanePositionSequence.ImagePositionPatient.map(
|
|
||||||
Number
|
|
||||||
);
|
|
||||||
let lastPosition = perFrame[
|
|
||||||
perFrame.length - 1
|
|
||||||
].PlanePositionSequence.ImagePositionPatient.map(Number);
|
|
||||||
geometry.sliceStep = [];
|
|
||||||
vtkMath.subtract(lastPosition, firstPosition, geometry.sliceStep);
|
|
||||||
vtkMath.normalize(geometry.sliceStep);
|
|
||||||
geometry.direction = columnStepToPatient
|
|
||||||
.concat(rowStepToPatient)
|
|
||||||
.concat(geometry.sliceStep);
|
|
||||||
geometry.origin = planePosition.ImagePositionPatient.map(Number);
|
|
||||||
|
|
||||||
return geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getSegments(dataset) {
|
|
||||||
const segments = {};
|
|
||||||
|
|
||||||
dataset.SegmentSequence.forEach(segment => {
|
|
||||||
const cielab = segment.RecommendedDisplayCIELabValue;
|
|
||||||
const rgba = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x =>
|
|
||||||
Math.round(x * 255)
|
|
||||||
);
|
|
||||||
|
|
||||||
rgba.push(255);
|
|
||||||
const segmentNumber = segment.SegmentNumber;
|
|
||||||
|
|
||||||
segments[segmentNumber] = {
|
|
||||||
color: rgba,
|
|
||||||
functionalGroups: [],
|
|
||||||
offset: null,
|
|
||||||
size: null,
|
|
||||||
pixelData: null,
|
|
||||||
label: segment.SegmentLabel,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// make a list of functional groups per segment
|
|
||||||
dataset.PerFrameFunctionalGroupsSequence.forEach(functionalGroup => {
|
|
||||||
const segmentNumber =
|
|
||||||
functionalGroup.SegmentIdentificationSequence.ReferencedSegmentNumber;
|
|
||||||
segments[segmentNumber].functionalGroups.push(functionalGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
return _getPixelData(dataset, segments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
const getDisplaySetsFromSeries = instances => {
|
const getDisplaySetsFromSeries = instances => {
|
||||||
return _getDisplaySetsFromSeries(
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { id } from './id';
|
import { id } from './id';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Types } from '@ohif/core';
|
|
||||||
|
|
||||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
import PanelSegmentation from './panels/PanelSegmentation';
|
|
||||||
import getHangingProtocolModule from './getHangingProtocolModule';
|
import getHangingProtocolModule from './getHangingProtocolModule';
|
||||||
|
import getPanelModule from './getPanelModule';
|
||||||
|
import getCommandsModule from './commandsModule';
|
||||||
|
import preRegistration from './init';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport');
|
||||||
/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const OHIFCornerstoneSEGViewport = props => {
|
const OHIFCornerstoneSEGViewport = props => {
|
||||||
@ -30,6 +28,7 @@ const extension = {
|
|||||||
* You ID can be anything you want, but it should be unique.
|
* You ID can be anything you want, but it should be unique.
|
||||||
*/
|
*/
|
||||||
id,
|
id,
|
||||||
|
preRegistration,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PanelModule should provide a list of panels that will be available in OHIF
|
* PanelModule should provide a list of panels that will be available in OHIF
|
||||||
@ -37,31 +36,8 @@ const extension = {
|
|||||||
* iconName, iconLabel, label, component} object. Example of a panel module
|
* iconName, iconLabel, label, component} object. Example of a panel module
|
||||||
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
||||||
*/
|
*/
|
||||||
getPanelModule: ({
|
getPanelModule,
|
||||||
servicesManager,
|
getCommandsModule,
|
||||||
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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
getViewportModule({ servicesManager, extensionManager }) {
|
getViewportModule({ servicesManager, extensionManager }) {
|
||||||
const ExtendedOHIFCornerstoneSEGViewport = props => {
|
const ExtendedOHIFCornerstoneSEGViewport = props => {
|
||||||
@ -69,14 +45,13 @@ const extension = {
|
|||||||
<OHIFCornerstoneSEGViewport
|
<OHIFCornerstoneSEGViewport
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
extensionManager={extensionManager}
|
extensionManager={extensionManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }];
|
||||||
{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport },
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
||||||
@ -88,4 +63,4 @@ const extension = {
|
|||||||
getHangingProtocolModule,
|
getHangingProtocolModule,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default extension;
|
export default extension;
|
||||||
|
|||||||
5
extensions/cornerstone-dicom-seg/src/init.ts
Normal file
5
extensions/cornerstone-dicom-seg/src/init.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { addTool, BrushTool } from '@cornerstonejs/tools';
|
||||||
|
|
||||||
|
export default function init({ configuration = {} }): void {
|
||||||
|
addTool(BrushTool);
|
||||||
|
}
|
||||||
@ -1,48 +1,28 @@
|
|||||||
|
import { createReportAsync } from '@ohif/extension-default';
|
||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { SegmentationGroupTable } from '@ohif/ui';
|
import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui';
|
||||||
import callInputDialog from './callInputDialog';
|
|
||||||
|
|
||||||
|
import callInputDialog from './callInputDialog';
|
||||||
|
import callColorPickerDialog from './colorPickerDialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function PanelSegmentation({
|
export default function PanelSegmentation({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
commandsManager,
|
commandsManager,
|
||||||
|
extensionManager,
|
||||||
|
configuration,
|
||||||
}) {
|
}) {
|
||||||
const { segmentationService, uiDialogService } = servicesManager.services;
|
const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services;
|
||||||
|
|
||||||
const { t } = useTranslation('PanelSegmentation');
|
const { t } = useTranslation('PanelSegmentation');
|
||||||
|
|
||||||
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
|
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
|
||||||
const [segmentationConfiguration, setSegmentationConfiguration] = useState(
|
const [segmentationConfiguration, setSegmentationConfiguration] = useState(
|
||||||
segmentationService.getConfiguration()
|
segmentationService.getConfiguration()
|
||||||
);
|
);
|
||||||
|
|
||||||
const [segmentations, setSegmentations] = useState(() =>
|
const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
// ~~ Subscription
|
// ~~ Subscription
|
||||||
@ -67,6 +47,16 @@ export default function PanelSegmentation({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getToolGroupIds = segmentationId => {
|
||||||
|
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
|
||||||
|
|
||||||
|
return toolGroupIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSegmentationAdd = async () => {
|
||||||
|
commandsManager.runCommand('createEmptySegmentationForViewport');
|
||||||
|
};
|
||||||
|
|
||||||
const onSegmentationClick = (segmentationId: string) => {
|
const onSegmentationClick = (segmentationId: string) => {
|
||||||
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
|
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
|
||||||
};
|
};
|
||||||
@ -75,33 +65,19 @@ export default function PanelSegmentation({
|
|||||||
segmentationService.remove(segmentationId);
|
segmentationService.remove(segmentationId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getToolGroupIds = segmentationId => {
|
const onSegmentAdd = segmentationId => {
|
||||||
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(
|
segmentationService.addSegment(segmentationId);
|
||||||
segmentationId
|
|
||||||
);
|
|
||||||
|
|
||||||
return toolGroupIds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSegmentClick = (segmentationId, segmentIndex) => {
|
const onSegmentClick = (segmentationId, segmentIndex) => {
|
||||||
segmentationService.setActiveSegmentForSegmentation(
|
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
||||||
segmentationId,
|
|
||||||
segmentIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
const toolGroupIds = getToolGroupIds(segmentationId);
|
const toolGroupIds = getToolGroupIds(segmentationId);
|
||||||
|
|
||||||
toolGroupIds.forEach(toolGroupId => {
|
toolGroupIds.forEach(toolGroupId => {
|
||||||
// const toolGroupId =
|
// const toolGroupId =
|
||||||
segmentationService.setActiveSegmentationForToolGroup(
|
segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
|
||||||
segmentationId,
|
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId);
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
segmentationService.jumpToSegmentCenter(
|
|
||||||
segmentationId,
|
|
||||||
segmentIndex,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,11 +92,7 @@ export default function PanelSegmentation({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentationService.setSegmentLabelForSegmentation(
|
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
|
||||||
segmentationId,
|
|
||||||
segmentIndex,
|
|
||||||
label
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -145,16 +117,34 @@ export default function PanelSegmentation({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSegmentColorClick = (segmentationId, segmentIndex) => {
|
const onSegmentColorClick = (segmentationId, segmentIndex) => {
|
||||||
// Todo: Implement color picker later
|
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||||
return;
|
|
||||||
|
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) => {
|
const onSegmentDelete = (segmentationId, segmentIndex) => {
|
||||||
// segmentationService.removeSegmentFromSegmentation(
|
segmentationService.removeSegment(segmentationId, segmentIndex);
|
||||||
// segmentationId,
|
|
||||||
// segmentIndex
|
|
||||||
// );
|
|
||||||
console.warn('not implemented yet');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
|
const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
|
||||||
@ -174,6 +164,10 @@ export default function PanelSegmentation({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onToggleSegmentLock = (segmentationId, segmentIndex) => {
|
||||||
|
segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
|
||||||
|
};
|
||||||
|
|
||||||
const onToggleSegmentationVisibility = segmentationId => {
|
const onToggleSegmentationVisibility = segmentationId => {
|
||||||
segmentationService.toggleSegmentationVisibility(segmentationId);
|
segmentationService.toggleSegmentationVisibility(segmentationId);
|
||||||
};
|
};
|
||||||
@ -188,47 +182,78 @@ export default function PanelSegmentation({
|
|||||||
[segmentationService]
|
[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 (
|
return (
|
||||||
<div className="flex flex-col flex-auto min-h-0 justify-between mt-1">
|
<>
|
||||||
{/* show segmentation table */}
|
<div className="ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto">
|
||||||
{segmentations?.length ? (
|
|
||||||
<SegmentationGroupTable
|
<SegmentationGroupTable
|
||||||
title={t('Segmentations')}
|
title={t('Segmentations')}
|
||||||
showAddSegmentation={false}
|
|
||||||
segmentations={segmentations}
|
segmentations={segmentations}
|
||||||
isMinimized={isMinimized}
|
disableEditing={configuration.disableEditing}
|
||||||
activeSegmentationId={selectedSegmentationId || ''}
|
activeSegmentationId={selectedSegmentationId || ''}
|
||||||
|
onSegmentationAdd={onSegmentationAdd}
|
||||||
onSegmentationClick={onSegmentationClick}
|
onSegmentationClick={onSegmentationClick}
|
||||||
onSegmentationDelete={onSegmentationDelete}
|
onSegmentationDelete={onSegmentationDelete}
|
||||||
|
onSegmentationDownload={onSegmentationDownload}
|
||||||
|
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
|
||||||
|
storeSegmentation={storeSegmentation}
|
||||||
onSegmentationEdit={onSegmentationEdit}
|
onSegmentationEdit={onSegmentationEdit}
|
||||||
onSegmentClick={onSegmentClick}
|
onSegmentClick={onSegmentClick}
|
||||||
onSegmentEdit={onSegmentEdit}
|
onSegmentEdit={onSegmentEdit}
|
||||||
|
onSegmentAdd={onSegmentAdd}
|
||||||
onSegmentColorClick={onSegmentColorClick}
|
onSegmentColorClick={onSegmentColorClick}
|
||||||
onSegmentDelete={onSegmentDelete}
|
onSegmentDelete={onSegmentDelete}
|
||||||
onToggleSegmentVisibility={onToggleSegmentVisibility}
|
onToggleSegmentVisibility={onToggleSegmentVisibility}
|
||||||
|
onToggleSegmentLock={onToggleSegmentLock}
|
||||||
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
||||||
onToggleMinimizeSegmentation={onToggleMinimizeSegmentation}
|
showDeleteSegment={true}
|
||||||
segmentationConfig={{ initialConfig: segmentationConfiguration }}
|
segmentationConfig={{ initialConfig: segmentationConfiguration }}
|
||||||
setRenderOutline={value =>
|
setRenderOutline={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value)
|
||||||
selectedSegmentationId,
|
|
||||||
'renderOutline',
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setOutlineOpacityActive={value =>
|
setOutlineOpacityActive={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value)
|
||||||
selectedSegmentationId,
|
|
||||||
'outlineOpacity',
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setRenderFill={value =>
|
setRenderFill={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value)
|
||||||
selectedSegmentationId,
|
|
||||||
'renderFill',
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setRenderInactiveSegmentations={value =>
|
setRenderInactiveSegmentations={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(
|
||||||
@ -238,29 +263,17 @@ export default function PanelSegmentation({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
setOutlineWidthActive={value =>
|
setOutlineWidthActive={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value)
|
||||||
selectedSegmentationId,
|
|
||||||
'outlineWidthActive',
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setFillAlpha={value =>
|
setFillAlpha={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value)
|
||||||
selectedSegmentationId,
|
|
||||||
'fillAlpha',
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setFillAlphaInactive={value =>
|
setFillAlphaInactive={value =>
|
||||||
_setSegmentationConfiguration(
|
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
|
||||||
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;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input, Dialog } from '@ohif/ui';
|
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
|
||||||
|
|
||||||
function callInputDialog(uiDialogService, label, callback) {
|
function callInputDialog(uiDialogService, label, callback) {
|
||||||
const dialogId = 'enter-segment-label';
|
const dialogId = 'enter-segment-label';
|
||||||
@ -29,8 +29,8 @@ function callInputDialog(uiDialogService, label, callback) {
|
|||||||
noCloseButton: true,
|
noCloseButton: true,
|
||||||
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'cancel', text: 'Cancel', type: 'primary' },
|
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
|
||||||
{ id: 'save', text: 'Confirm', type: 'secondary' },
|
{ id: 'save', text: 'Confirm', type: ButtonEnums.type.primary },
|
||||||
],
|
],
|
||||||
onSubmit: onSubmitHandler,
|
onSubmit: onSubmitHandler,
|
||||||
body: ({ value, setValue }) => {
|
body: ({ value, setValue }) => {
|
||||||
@ -39,7 +39,7 @@ function callInputDialog(uiDialogService, label, callback) {
|
|||||||
label="Enter the segment label"
|
label="Enter the segment label"
|
||||||
labelClassName="text-white text-[14px] leading-[1.2]"
|
labelClassName="text-white text-[14px] leading-[1.2]"
|
||||||
autoFocus
|
autoFocus
|
||||||
className="bg-black border-primary-main"
|
className="border-primary-main bg-black"
|
||||||
type="text"
|
type="text"
|
||||||
value={value.label}
|
value={value.label}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
|
|||||||
@ -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,69 +0,0 @@
|
|||||||
async function _hydrateSEGDisplaySet({
|
|
||||||
segDisplaySet,
|
|
||||||
viewportIndex,
|
|
||||||
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(
|
|
||||||
viewportIndex,
|
|
||||||
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, index) => {
|
|
||||||
if (index === viewportIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
|
|
||||||
viewport.displaySetInstanceUIDs,
|
|
||||||
segDisplaySet.displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldDisplaySeg) {
|
|
||||||
updatedViewports.push({
|
|
||||||
viewportIndex: index,
|
|
||||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
|
||||||
viewportOptions: {
|
|
||||||
initialImageOptions: {
|
|
||||||
preset: 'middle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do the entire update at once
|
|
||||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default _hydrateSEGDisplaySet;
|
|
||||||
14
extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts
Normal file
14
extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a CIELAB color to an RGB color using the dcmjs library.
|
||||||
|
* @param cielab - The CIELAB color to convert.
|
||||||
|
* @returns The RGB color as an array of three integers between 0 and 255.
|
||||||
|
*/
|
||||||
|
function dicomlabToRGB(cielab: number[]): number[] {
|
||||||
|
const rgb = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => Math.round(x * 255));
|
||||||
|
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { dicomlabToRGB };
|
||||||
190
extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts
Normal file
190
extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts
Normal file
@ -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,12 +1,7 @@
|
|||||||
function createSEGToolGroupAndAddTools(
|
function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
|
||||||
ToolGroupService,
|
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
||||||
customizationService,
|
|
||||||
toolGroupId
|
|
||||||
) {
|
|
||||||
const { tools } =
|
|
||||||
customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
|
||||||
|
|
||||||
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {});
|
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createSEGToolGroupAndAddTools;
|
export default createSEGToolGroupAndAddTools;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import hydrateSEGDisplaySet from './_hydrateSEG';
|
import { ButtonEnums } from '@ohif/ui';
|
||||||
|
|
||||||
const RESPONSE = {
|
const RESPONSE = {
|
||||||
NO_NEVER: -1,
|
NO_NEVER: -1,
|
||||||
@ -9,21 +9,23 @@ const RESPONSE = {
|
|||||||
function promptHydrateSEG({
|
function promptHydrateSEG({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
|
preHydrateCallbacks,
|
||||||
|
hydrateSEGDisplaySet,
|
||||||
}) {
|
}) {
|
||||||
const { uiViewportDialogService } = servicesManager.services;
|
const { uiViewportDialogService } = servicesManager.services;
|
||||||
|
|
||||||
return new Promise(async function(resolve, reject) {
|
return new Promise(async function (resolve, reject) {
|
||||||
const promptResult = await _askHydrate(
|
const promptResult = await _askHydrate(uiViewportDialogService, viewportId);
|
||||||
uiViewportDialogService,
|
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||||
|
preHydrateCallbacks?.forEach(callback => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
const isHydrated = await hydrateSEGDisplaySet({
|
const isHydrated = await hydrateSEGDisplaySet({
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve(isHydrated);
|
resolve(isHydrated);
|
||||||
@ -31,17 +33,17 @@ function promptHydrateSEG({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _askHydrate(uiViewportDialogService, viewportIndex) {
|
function _askHydrate(uiViewportDialogService, viewportId) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
const message = 'Do you want to open this Segmentation?';
|
const message = 'Do you want to open this Segmentation?';
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
type: 'secondary',
|
type: ButtonEnums.type.secondary,
|
||||||
text: 'No',
|
text: 'No',
|
||||||
value: RESPONSE.CANCEL,
|
value: RESPONSE.CANCEL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'primary',
|
type: ButtonEnums.type.primary,
|
||||||
text: 'Yes',
|
text: 'Yes',
|
||||||
value: RESPONSE.HYDRATE_SEG,
|
value: RESPONSE.HYDRATE_SEG,
|
||||||
},
|
},
|
||||||
@ -52,7 +54,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
uiViewportDialogService.show({
|
uiViewportDialogService.show({
|
||||||
viewportIndex,
|
viewportId,
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message,
|
message,
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@ -2,14 +2,9 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import OHIF, { utils } from '@ohif/core';
|
import OHIF, { utils } from '@ohif/core';
|
||||||
import {
|
import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui';
|
||||||
LoadingIndicatorTotalPercent,
|
|
||||||
useViewportGrid,
|
|
||||||
ViewportActionBar,
|
|
||||||
} from '@ohif/ui';
|
|
||||||
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
||||||
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
||||||
import hydrateSEGDisplaySet from '../utils/_hydrateSEG';
|
|
||||||
import _getStatusComponent from './_getStatusComponent';
|
import _getStatusComponent from './_getStatusComponent';
|
||||||
|
|
||||||
const { formatDate } = utils;
|
const { formatDate } = utils;
|
||||||
@ -20,13 +15,14 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
children,
|
children,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
viewportIndex,
|
|
||||||
viewportLabel,
|
viewportLabel,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
extensionManager,
|
extensionManager,
|
||||||
|
commandsManager,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { t } = useTranslation('SEGViewport');
|
const { t } = useTranslation('SEGViewport');
|
||||||
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
displaySetService,
|
displaySetService,
|
||||||
@ -36,7 +32,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
customizationService,
|
customizationService,
|
||||||
} = servicesManager.services;
|
} = servicesManager.services;
|
||||||
|
|
||||||
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`;
|
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`;
|
||||||
|
|
||||||
// SEG viewport will always have a single display set
|
// SEG viewport will always have a single display set
|
||||||
if (displaySets.length > 1) {
|
if (displaySets.length > 1) {
|
||||||
@ -48,7 +44,6 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [isToolGroupCreated, setToolGroupCreated] = useState(false);
|
|
||||||
const [selectedSegment, setSelectedSegment] = useState(1);
|
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||||
|
|
||||||
// Hydration means that the SEG is opened and segments are loaded into the
|
// Hydration means that the SEG is opened and segments are loaded into the
|
||||||
@ -67,11 +62,12 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
// refs
|
// refs
|
||||||
const referencedDisplaySetRef = useRef(null);
|
const referencedDisplaySetRef = useRef(null);
|
||||||
|
|
||||||
const { viewports, activeViewportIndex } = viewportGrid;
|
const { viewports, activeViewportId } = viewportGrid;
|
||||||
|
|
||||||
const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet();
|
const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet();
|
||||||
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
||||||
referencedDisplaySet
|
referencedDisplaySet,
|
||||||
|
segDisplaySet
|
||||||
);
|
);
|
||||||
|
|
||||||
referencedDisplaySetRef.current = {
|
referencedDisplaySetRef.current = {
|
||||||
@ -92,14 +88,20 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
setElement(null);
|
setElement(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storePresentationState = useCallback(() => {
|
||||||
|
viewportGrid?.viewports.forEach(({ viewportId }) => {
|
||||||
|
commandsManager.runCommand('storePresentation', {
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [viewportGrid]);
|
||||||
|
|
||||||
const getCornerstoneViewport = useCallback(() => {
|
const getCornerstoneViewport = useCallback(() => {
|
||||||
const { component: Component } = extensionManager.getModuleEntry(
|
const { component: Component } = extensionManager.getModuleEntry(
|
||||||
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||||
displaySet: referencedDisplaySet,
|
|
||||||
} = referencedDisplaySetRef.current;
|
|
||||||
|
|
||||||
// Todo: jump to the center of the first segment
|
// Todo: jump to the center of the first segment
|
||||||
return (
|
return (
|
||||||
@ -117,7 +119,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
// initialImageIndex={initialImageIndex}
|
// initialImageIndex={initialImageIndex}
|
||||||
></Component>
|
></Component>
|
||||||
);
|
);
|
||||||
}, [viewportIndex, segDisplaySet, toolGroupId]);
|
}, [viewportId, segDisplaySet, toolGroupId]);
|
||||||
|
|
||||||
const onSegmentChange = useCallback(
|
const onSegmentChange = useCallback(
|
||||||
direction => {
|
direction => {
|
||||||
@ -139,11 +141,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
newSelectedSegmentIndex = numberOfSegments - 1;
|
newSelectedSegmentIndex = numberOfSegments - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentationService.jumpToSegmentCenter(
|
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId);
|
||||||
segmentationId,
|
|
||||||
newSelectedSegmentIndex,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
setSelectedSegment(newSelectedSegmentIndex);
|
setSelectedSegment(newSelectedSegmentIndex);
|
||||||
},
|
},
|
||||||
[selectedSegment]
|
[selectedSegment]
|
||||||
@ -156,31 +154,29 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
|
|
||||||
promptHydrateSEG({
|
promptHydrateSEG({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
|
preHydrateCallbacks: [storePresentationState],
|
||||||
|
hydrateSEGDisplaySet,
|
||||||
}).then(isHydrated => {
|
}).then(isHydrated => {
|
||||||
if (isHydrated) {
|
if (isHydrated) {
|
||||||
setIsHydrated(true);
|
setIsHydrated(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]);
|
}, [servicesManager, viewportId, segDisplaySet, segIsLoading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = segmentationService.subscribe(
|
const { unsubscribe } = segmentationService.subscribe(
|
||||||
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||||
evt => {
|
evt => {
|
||||||
if (
|
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
|
||||||
evt.segDisplaySet.displaySetInstanceUID ===
|
|
||||||
segDisplaySet.displaySetInstanceUID
|
|
||||||
) {
|
|
||||||
setSegIsLoading(false);
|
setSegIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.overlappingSegments) {
|
if (evt.overlappingSegments) {
|
||||||
uiNotificationService.show({
|
uiNotificationService.show({
|
||||||
title: 'Overlapping Segments',
|
title: 'Overlapping Segments',
|
||||||
message:
|
message: 'Overlapping segments detected which is not currently supported',
|
||||||
'Overlapping segments detected which is not currently supported',
|
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -215,12 +211,10 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||||
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||||
({ displaySetInstanceUIDs }) => {
|
({ displaySetInstanceUIDs }) => {
|
||||||
const activeViewport = viewports[activeViewportIndex];
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
if (
|
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||||
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
|
|
||||||
) {
|
|
||||||
viewportGridService.setDisplaySetsForViewport({
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
viewportIndex: activeViewportIndex,
|
viewportId: activeViewportId,
|
||||||
displaySetInstanceUIDs: [],
|
displaySetInstanceUIDs: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -241,19 +235,11 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
|
|
||||||
// This creates a custom tool group which has the lifetime of this view
|
// This creates a custom tool group which has the lifetime of this view
|
||||||
// only, and does NOT interfere with currently displayed segmentations.
|
// only, and does NOT interfere with currently displayed segmentations.
|
||||||
toolGroup = createSEGToolGroupAndAddTools(
|
toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||||
toolGroupService,
|
|
||||||
customizationService,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
|
|
||||||
setToolGroupCreated(true);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove the segmentation representations if seg displayset changed
|
// remove the segmentation representations if seg displayset changed
|
||||||
segmentationService.removeSegmentationRepresentationFromToolGroup(
|
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only destroy the viewport specific implementation
|
// Only destroy the viewport specific implementation
|
||||||
toolGroupService.destroyToolGroup(toolGroupId);
|
toolGroupService.destroyToolGroup(toolGroupId);
|
||||||
@ -265,9 +251,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove the segmentation representations if seg displayset changed
|
// remove the segmentation representations if seg displayset changed
|
||||||
segmentationService.removeSegmentationRepresentationFromToolGroup(
|
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
referencedDisplaySetRef.current = null;
|
referencedDisplaySetRef.current = null;
|
||||||
};
|
};
|
||||||
}, [segDisplaySet]);
|
}, [segDisplaySet]);
|
||||||
@ -288,7 +272,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
return (
|
return (
|
||||||
child &&
|
child &&
|
||||||
React.cloneElement(child, {
|
React.cloneElement(child, {
|
||||||
viewportIndex,
|
viewportId,
|
||||||
key: index,
|
key: index,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -307,16 +291,28 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
SpacingBetweenSlices,
|
SpacingBetweenSlices,
|
||||||
} = referencedDisplaySetRef.current.metadata;
|
} = referencedDisplaySetRef.current.metadata;
|
||||||
|
|
||||||
|
const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => {
|
||||||
|
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||||
|
displaySets: [segDisplaySet],
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onStatusClick = async () => {
|
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
|
||||||
|
// presentation state, so that we can restore it after hydrating the SEG. This is
|
||||||
|
// required if the user has changed the viewport (other viewport than SEG viewport)
|
||||||
|
// presentation state (w/l and invert) and then opens the SEG. If we don't store
|
||||||
|
// the presentation state, the viewport will be reset to the default presentation
|
||||||
|
storePresentationState();
|
||||||
const isHydrated = await hydrateSEGDisplaySet({
|
const isHydrated = await hydrateSEGDisplaySet({
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsHydrated(isHydrated);
|
setIsHydrated(isHydrated);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewportActionBar
|
<ViewportActionBar
|
||||||
@ -337,26 +333,23 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
studyDate: formatDate(StudyDate),
|
studyDate: formatDate(StudyDate),
|
||||||
seriesDescription: `SEG Viewport ${SeriesDescription}`,
|
seriesDescription: `SEG Viewport ${SeriesDescription}`,
|
||||||
patientInformation: {
|
patientInformation: {
|
||||||
patientName: PatientName
|
patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '',
|
||||||
? OHIF.utils.formatPN(PatientName.Alphabetic)
|
|
||||||
: '',
|
|
||||||
patientSex: PatientSex || '',
|
patientSex: PatientSex || '',
|
||||||
patientAge: PatientAge || '',
|
patientAge: PatientAge || '',
|
||||||
MRN: PatientID || '',
|
MRN: PatientID || '',
|
||||||
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
|
thickness: SliceThickness ? utils.roundNumber(SliceThickness, 2) : '',
|
||||||
|
thicknessUnits: SliceThickness !== undefined ? 'mm' : '',
|
||||||
spacing:
|
spacing:
|
||||||
SpacingBetweenSlices !== undefined
|
SpacingBetweenSlices !== undefined ? utils.roundNumber(SpacingBetweenSlices, 2) : '',
|
||||||
? `${SpacingBetweenSlices.toFixed(2)}mm`
|
|
||||||
: '',
|
|
||||||
scanner: ManufacturerModelName || '',
|
scanner: ManufacturerModelName || '',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative flex flex-row w-full h-full overflow-hidden">
|
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||||
{segIsLoading && (
|
{segIsLoading && (
|
||||||
<LoadingIndicatorTotalPercent
|
<LoadingIndicatorTotalPercent
|
||||||
className="w-full h-full"
|
className="h-full w-full"
|
||||||
totalNumbers={processingProgress.totalSegments}
|
totalNumbers={processingProgress.totalSegments}
|
||||||
percentComplete={processingProgress.percentComplete}
|
percentComplete={processingProgress.percentComplete}
|
||||||
loadingText="Loading SEG..."
|
loadingText="Loading SEG..."
|
||||||
@ -371,7 +364,7 @@ function OHIFCornerstoneSEGViewport(props) {
|
|||||||
|
|
||||||
OHIFCornerstoneSEGViewport.propTypes = {
|
OHIFCornerstoneSEGViewport.propTypes = {
|
||||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
viewportIndex: PropTypes.number.isRequired,
|
viewportId: PropTypes.string.isRequired,
|
||||||
dataSource: PropTypes.object,
|
dataSource: PropTypes.object,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
customProps: PropTypes.object,
|
customProps: PropTypes.object,
|
||||||
@ -381,20 +374,34 @@ OHIFCornerstoneSEGViewport.defaultProps = {
|
|||||||
customProps: {},
|
customProps: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function _getReferencedDisplaySetMetadata(referencedDisplaySet) {
|
function _getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) {
|
||||||
|
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];
|
const image0 = referencedDisplaySet.images[0];
|
||||||
const referencedDisplaySetMetadata = {
|
const referencedDisplaySetMetadata = {
|
||||||
PatientID: image0.PatientID,
|
PatientID: image0.PatientID,
|
||||||
PatientName: image0.PatientName,
|
PatientName: image0.PatientName,
|
||||||
PatientSex: image0.PatientSex,
|
PatientSex: image0.PatientSex,
|
||||||
PatientAge: image0.PatientAge,
|
PatientAge: image0.PatientAge,
|
||||||
SliceThickness: image0.SliceThickness,
|
SliceThickness: image0.SliceThickness || SliceThickness,
|
||||||
StudyDate: image0.StudyDate,
|
StudyDate: image0.StudyDate,
|
||||||
SeriesDescription: image0.SeriesDescription,
|
SeriesDescription: image0.SeriesDescription,
|
||||||
SeriesInstanceUID: image0.SeriesInstanceUID,
|
SeriesInstanceUID: image0.SeriesInstanceUID,
|
||||||
SeriesNumber: image0.SeriesNumber,
|
SeriesNumber: image0.SeriesNumber,
|
||||||
ManufacturerModelName: image0.ManufacturerModelName,
|
ManufacturerModelName: image0.ManufacturerModelName,
|
||||||
SpacingBetweenSlices: image0.SpacingBetweenSlices,
|
SpacingBetweenSlices: image0.SpacingBetweenSlices || SpacingBetweenSlices,
|
||||||
};
|
};
|
||||||
|
|
||||||
return referencedDisplaySetMetadata;
|
return referencedDisplaySetMetadata;
|
||||||
|
|||||||
@ -2,37 +2,39 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon, Tooltip } from '@ohif/ui';
|
import { Icon, Tooltip } from '@ohif/ui';
|
||||||
|
|
||||||
|
|
||||||
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
let ToolTipMessage = null;
|
let ToolTipMessage = null;
|
||||||
let StatusIcon = null;
|
let StatusIcon = null;
|
||||||
|
|
||||||
const {t} = useTranslation("Common");
|
const { t } = useTranslation('Common');
|
||||||
const loadStr = t("LOAD");
|
const loadStr = t('LOAD');
|
||||||
|
|
||||||
switch (isHydrated) {
|
switch (isHydrated) {
|
||||||
case true:
|
case true:
|
||||||
StatusIcon = () => <Icon name="status-alert" />;
|
StatusIcon = () => <Icon name="status-alert" />;
|
||||||
|
|
||||||
ToolTipMessage = () => (
|
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
|
||||||
<div>This Segmentation is loaded in the segmentation panel</div>
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case false:
|
case false:
|
||||||
StatusIcon = () => <Icon name="status-untracked" />;
|
StatusIcon = () => (
|
||||||
|
<Icon
|
||||||
|
className="text-aqua-pale"
|
||||||
|
name="status-untracked"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>;
|
ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusArea = () => (
|
const StatusArea = () => (
|
||||||
<div className="flex h-6 leading-6 cursor-default text-sm text-white">
|
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
||||||
<div className="min-w-[45px] flex items-center p-1 rounded-l-xl rounded-r bg-customgray-100">
|
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
||||||
<StatusIcon />
|
<StatusIcon />
|
||||||
<span className="ml-1">SEG</span>
|
<span className="ml-1">SEG</span>
|
||||||
</div>
|
</div>
|
||||||
{!isHydrated && (
|
{!isHydrated && (
|
||||||
<div
|
<div
|
||||||
className="ml-1 px-1.5 rounded cursor-pointer hover:text-black bg-primary-main hover:bg-primary-light"
|
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
||||||
onMouseUp={onStatusClick}
|
onMouseUp={onStatusClick}
|
||||||
>
|
>
|
||||||
@ -42,11 +44,13 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ToolTipMessage && (
|
{ToolTipMessage && (
|
||||||
<Tooltip content={<ToolTipMessage />} position="bottom-left">
|
<Tooltip
|
||||||
|
content={<ToolTipMessage />}
|
||||||
|
position="bottom-left"
|
||||||
|
>
|
||||||
<StatusArea />
|
<StatusArea />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -41,13 +41,7 @@ module.exports = (env, argv) => {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
filename: pkg.main,
|
filename: pkg.main,
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
/\b(vtk.js)/,
|
|
||||||
/\b(dcmjs)/,
|
|
||||||
/\b(gl-matrix)/,
|
|
||||||
/^@ohif/,
|
|
||||||
/^@cornerstonejs/,
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
maxChunks: 1,
|
maxChunks: 1,
|
||||||
|
|||||||
468
extensions/cornerstone-dicom-sr/CHANGELOG.md
Normal file
468
extensions/cornerstone-dicom-sr/CHANGELOG.md
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8))
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||||
"version": "3.6.0",
|
"version": "3.7.0-beta.110",
|
||||||
"description": "OHIF extension for an SR Cornerstone Viewport",
|
"description": "OHIF extension for an SR Cornerstone Viewport",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -32,10 +32,10 @@
|
|||||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "3.6.0",
|
"@ohif/core": "3.7.0-beta.110",
|
||||||
"@ohif/extension-cornerstone": "3.6.0",
|
"@ohif/extension-cornerstone": "3.7.0-beta.110",
|
||||||
"@ohif/extension-measurement-tracking": "3.6.0",
|
"@ohif/extension-measurement-tracking": "3.7.0-beta.110",
|
||||||
"@ohif/ui": "3.6.0",
|
"@ohif/ui": "3.7.0-beta.110",
|
||||||
"dcmjs": "^0.29.5",
|
"dcmjs": "^0.29.5",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
@ -44,9 +44,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.1.0",
|
"@cornerstonejs/adapters": "^1.20.3",
|
||||||
"@cornerstonejs/core": "^1.1.0",
|
"@cornerstonejs/core": "^1.20.3",
|
||||||
"@cornerstonejs/tools": "^1.1.0",
|
"@cornerstonejs/tools": "^1.20.3",
|
||||||
"classnames": "^2.3.2"
|
"classnames": "^2.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,11 +17,7 @@ const { log } = OHIF;
|
|||||||
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const _generateReport = (
|
const _generateReport = (measurementData, additionalFindingTypes, options = {}) => {
|
||||||
measurementData,
|
|
||||||
additionalFindingTypes,
|
|
||||||
options = {}
|
|
||||||
) => {
|
|
||||||
const filteredToolState = getFilteredCornerstoneToolState(
|
const filteredToolState = getFilteredCornerstoneToolState(
|
||||||
measurementData,
|
measurementData,
|
||||||
additionalFindingTypes
|
additionalFindingTypes
|
||||||
@ -54,16 +50,8 @@ const commandsModule = ({}) => {
|
|||||||
* as opposed to Finding Sites.
|
* as opposed to Finding Sites.
|
||||||
* that you wish to serialize.
|
* that you wish to serialize.
|
||||||
*/
|
*/
|
||||||
downloadReport: ({
|
downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => {
|
||||||
measurementData,
|
const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options);
|
||||||
additionalFindingTypes,
|
|
||||||
options = {},
|
|
||||||
}) => {
|
|
||||||
const srDataset = actions.generateReport(
|
|
||||||
measurementData,
|
|
||||||
additionalFindingTypes,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
|
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
|
||||||
|
|
||||||
//Create a URL for the binary.
|
//Create a URL for the binary.
|
||||||
@ -91,28 +79,19 @@ const commandsModule = ({}) => {
|
|||||||
log.info('[DICOMSR] storeMeasurements');
|
log.info('[DICOMSR] storeMeasurements');
|
||||||
|
|
||||||
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
||||||
log.error(
|
log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
|
||||||
'[DICOMSR] datasource has no dataSource.store.dicom endpoint!'
|
|
||||||
);
|
|
||||||
return Promise.reject({});
|
return Promise.reject({});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const naturalizedReport = _generateReport(
|
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
|
||||||
measurementData,
|
|
||||||
additionalFindingTypes,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
const { StudyInstanceUID, ContentSequence } = naturalizedReport;
|
const { StudyInstanceUID, ContentSequence } = naturalizedReport;
|
||||||
// The content sequence has 5 or more elements, of which
|
// The content sequence has 5 or more elements, of which
|
||||||
// the `[4]` element contains the annotation data, so this is
|
// the `[4]` element contains the annotation data, so this is
|
||||||
// checking that there is some annotation data present.
|
// checking that there is some annotation data present.
|
||||||
if (!ContentSequence?.[4].ContentSequence?.length) {
|
if (!ContentSequence?.[4].ContentSequence?.length) {
|
||||||
console.log(
|
console.log('naturalizedReport missing imaging content', naturalizedReport);
|
||||||
'naturalizedReport missing imaging content',
|
|
||||||
naturalizedReport
|
|
||||||
);
|
|
||||||
throw new Error('Invalid report, no content');
|
throw new Error('Invalid report, no content');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,12 +109,8 @@ const commandsModule = ({}) => {
|
|||||||
return naturalizedReport;
|
return naturalizedReport;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
log.error(
|
log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
||||||
`[DICOMSR] Error while saving the measurements: ${error.message}`
|
throw new Error(error.message || 'Error while saving the measurements.');
|
||||||
);
|
|
||||||
throw new Error(
|
|
||||||
error.message || 'Error while saving the measurements.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,6 @@ const srProtocol: Types.HangingProtocol.Protocol = {
|
|||||||
// Don't store this hanging protocol as it applies to the currently active
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
// display set by default
|
// display set by default
|
||||||
// cacheId: null,
|
// cacheId: null,
|
||||||
hasUpdatedPriorsInformation: false,
|
|
||||||
name: 'SR Key Images',
|
name: 'SR Key Images',
|
||||||
// Just apply this one when specifically listed
|
// Just apply this one when specifically listed
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
|
|||||||
@ -29,9 +29,7 @@ const validateSameStudyUID = (uid: string, instances): void => {
|
|||||||
instances.forEach(it => {
|
instances.forEach(it => {
|
||||||
if (it.StudyInstanceUID !== uid) {
|
if (it.StudyInstanceUID !== uid) {
|
||||||
console.warn('Not all instances have the same UID', uid, it);
|
console.warn('Not all instances have the same UID', uid, it);
|
||||||
throw new Error(
|
throw new Error(`Instances ${it.SOPInstanceUID} does not belong to ${uid}`);
|
||||||
`Instances ${it.SOPInstanceUID} does not belong to ${uid}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -51,10 +49,7 @@ const CodeNameCodeSequenceValues = {
|
|||||||
|
|
||||||
const CodingSchemeDesignators = {
|
const CodingSchemeDesignators = {
|
||||||
SRT: 'SRT',
|
SRT: 'SRT',
|
||||||
CornerstoneCodeSchemes: [
|
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||||
Cornerstone3DCodeScheme.CodingSchemeDesignator,
|
|
||||||
'CST4',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RELATIONSHIP_TYPE = {
|
const RELATIONSHIP_TYPE = {
|
||||||
@ -71,10 +66,7 @@ const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
|||||||
* @param instances is a list of instances from THIS series that are not
|
* @param instances is a list of instances from THIS series that are not
|
||||||
* in this DICOM SR Display Set already.
|
* in this DICOM SR Display Set already.
|
||||||
*/
|
*/
|
||||||
function addInstances(
|
function addInstances(instances: InstanceMetadata[], displaySetService: DisplaySetService) {
|
||||||
instances: InstanceMetadata[],
|
|
||||||
displaySetService: DisplaySetService
|
|
||||||
) {
|
|
||||||
this.instances.push(...instances);
|
this.instances.push(...instances);
|
||||||
utils.sortStudyInstances(this.instances);
|
utils.sortStudyInstances(this.instances);
|
||||||
// The last instance is the newest one, so is the one most interesting.
|
// The last instance is the newest one, so is the one most interesting.
|
||||||
@ -93,11 +85,7 @@ function addInstances(
|
|||||||
* @param servicesManager is the services that can be used for creating
|
* @param servicesManager is the services that can be used for creating
|
||||||
* @returns The list of display sets created for the given instances object
|
* @returns The list of display sets created for the given instances object
|
||||||
*/
|
*/
|
||||||
function _getDisplaySetsFromSeries(
|
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
) {
|
|
||||||
// If the series has no instances, stop here
|
// If the series has no instances, stop here
|
||||||
if (!instances || !instances.length) {
|
if (!instances || !instances.length) {
|
||||||
throw new Error('No instances were provided');
|
throw new Error('No instances were provided');
|
||||||
@ -123,8 +111,7 @@ function _getDisplaySetsFromSeries(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!ConceptNameCodeSequence ||
|
!ConceptNameCodeSequence ||
|
||||||
ConceptNameCodeSequence.CodeValue !==
|
ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport
|
||||||
CodeNameCodeSequenceValues.ImagingMeasurementReport
|
|
||||||
) {
|
) {
|
||||||
servicesManager.services.uiNotificationService.show({
|
servicesManager.services.uiNotificationService.show({
|
||||||
title: 'DICOM SR',
|
title: 'DICOM SR',
|
||||||
@ -184,36 +171,21 @@ function _load(displaySet, servicesManager, extensionManager) {
|
|||||||
|
|
||||||
// Check currently added displaySets and add measurements if the sources exist.
|
// Check currently added displaySets and add measurements if the sources exist.
|
||||||
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||||
_checkIfCanAddMeasurementsToDisplaySet(
|
_checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource);
|
||||||
displaySet,
|
|
||||||
activeDisplaySet,
|
|
||||||
dataSource
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to new displaySets as the source may come in after.
|
// Subscribe to new displaySets as the source may come in after.
|
||||||
displaySetService.subscribe(
|
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
||||||
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
const { displaySetsAdded } = data;
|
||||||
data => {
|
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||||
const { displaySetsAdded } = data;
|
// See if we can load them onto any of the new displaySets.
|
||||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
displaySetsAdded.forEach(newDisplaySet => {
|
||||||
// See if we can load them onto any of the new displaySets.
|
_checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource);
|
||||||
displaySetsAdded.forEach(newDisplaySet => {
|
});
|
||||||
_checkIfCanAddMeasurementsToDisplaySet(
|
});
|
||||||
displaySet,
|
|
||||||
newDisplaySet,
|
|
||||||
dataSource
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) {
|
||||||
srDisplaySet,
|
|
||||||
newDisplaySet,
|
|
||||||
dataSource
|
|
||||||
) {
|
|
||||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||||
measurement => measurement.loaded === false
|
measurement => measurement.loaded === false
|
||||||
);
|
);
|
||||||
@ -248,8 +220,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
const { coords } = measurement;
|
const { coords } = measurement;
|
||||||
|
|
||||||
coords.forEach(coord => {
|
coords.forEach(coord => {
|
||||||
const SOPInstanceUID =
|
const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||||
coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
||||||
|
|
||||||
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||||
SOPInstanceUIDs.push(SOPInstanceUID);
|
SOPInstanceUIDs.push(SOPInstanceUID);
|
||||||
@ -257,9 +228,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(
|
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
||||||
newDisplaySet
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const imageId of imageIdsForDisplaySet) {
|
for (const imageId of imageIdsForDisplaySet) {
|
||||||
if (!unloadedMeasurements.length) {
|
if (!unloadedMeasurements.length) {
|
||||||
@ -267,25 +236,13 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(
|
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId);
|
||||||
imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||||
const measurement = unloadedMeasurements[j];
|
const measurement = unloadedMeasurements[j];
|
||||||
if (
|
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||||
_measurementReferencesSOPInstanceUID(
|
addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID);
|
||||||
measurement,
|
|
||||||
SOPInstanceUID,
|
|
||||||
frameNumber
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
addMeasurement(
|
|
||||||
measurement,
|
|
||||||
imageId,
|
|
||||||
newDisplaySet.displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
unloadedMeasurements.splice(j, 1);
|
unloadedMeasurements.splice(j, 1);
|
||||||
}
|
}
|
||||||
@ -294,11 +251,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _measurementReferencesSOPInstanceUID(
|
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||||
measurement,
|
|
||||||
SOPInstanceUID,
|
|
||||||
frameNumber
|
|
||||||
) {
|
|
||||||
const { coords } = measurement;
|
const { coords } = measurement;
|
||||||
|
|
||||||
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||||
@ -308,8 +261,9 @@ function _measurementReferencesSOPInstanceUID(
|
|||||||
measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) ||
|
measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) ||
|
||||||
1;
|
1;
|
||||||
|
|
||||||
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber))
|
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (let j = 0; j < coords.length; j++) {
|
for (let j = 0; j < coords.length; j++) {
|
||||||
const coord = coords[j];
|
const coord = coords[j];
|
||||||
@ -323,11 +277,7 @@ function _measurementReferencesSOPInstanceUID(
|
|||||||
|
|
||||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
const getDisplaySetsFromSeries = instances => {
|
const getDisplaySetsFromSeries = instances => {
|
||||||
return _getDisplaySetsFromSeries(
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
instances,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -342,30 +292,22 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
|||||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||||
item =>
|
item =>
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements
|
||||||
CodeNameCodeSequenceValues.ImagingMeasurements
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const MeasurementGroups = _getSequenceAsArray(
|
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(
|
||||||
ImagingMeasurements.ContentSequence
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup
|
||||||
).filter(
|
|
||||||
item =>
|
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.MeasurementGroup
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
const mergedContentSequencesByTrackingUniqueIdentifiers =
|
||||||
MeasurementGroups
|
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
||||||
);
|
|
||||||
|
|
||||||
const measurements = [];
|
const measurements = [];
|
||||||
|
|
||||||
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||||
trackingUniqueIdentifier => {
|
trackingUniqueIdentifier => {
|
||||||
const mergedContentSequence =
|
const mergedContentSequence =
|
||||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
||||||
trackingUniqueIdentifier
|
|
||||||
];
|
|
||||||
|
|
||||||
const measurement = _processMeasurement(mergedContentSequence);
|
const measurement = _processMeasurement(mergedContentSequence);
|
||||||
|
|
||||||
@ -378,15 +320,11 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|||||||
return measurements;
|
return measurements;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||||
MeasurementGroups
|
|
||||||
) {
|
|
||||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||||
|
|
||||||
MeasurementGroups.forEach(MeasurementGroup => {
|
MeasurementGroups.forEach(MeasurementGroup => {
|
||||||
const ContentSequence = _getSequenceAsArray(
|
const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence);
|
||||||
MeasurementGroup.ContentSequence
|
|
||||||
);
|
|
||||||
|
|
||||||
const TrackingUniqueIdentifierItem = ContentSequence.find(
|
const TrackingUniqueIdentifierItem = ContentSequence.find(
|
||||||
item =>
|
item =>
|
||||||
@ -395,22 +333,16 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!TrackingUniqueIdentifierItem) {
|
if (!TrackingUniqueIdentifierItem) {
|
||||||
console.warn(
|
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
||||||
'No Tracking Unique Identifier, skipping ambiguous measurement.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID;
|
const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID;
|
||||||
|
|
||||||
if (
|
if (mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] === undefined) {
|
||||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
|
||||||
trackingUniqueIdentifier
|
|
||||||
] === undefined
|
|
||||||
) {
|
|
||||||
// Add the full ContentSequence
|
// Add the full ContentSequence
|
||||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] = [
|
||||||
trackingUniqueIdentifier
|
...ContentSequence,
|
||||||
] = [...ContentSequence];
|
];
|
||||||
} else {
|
} else {
|
||||||
// Add the ContentSequence minus the tracking identifier, as we have this
|
// Add the ContentSequence minus the tracking identifier, as we have this
|
||||||
// Information in the merged ContentSequence anyway.
|
// Information in the merged ContentSequence anyway.
|
||||||
@ -419,9 +351,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(
|
|||||||
item.ConceptNameCodeSequence.CodeValue !==
|
item.ConceptNameCodeSequence.CodeValue !==
|
||||||
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||||
) {
|
) {
|
||||||
mergedContentSequencesByTrackingUniqueIdentifiers[
|
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item);
|
||||||
trackingUniqueIdentifier
|
|
||||||
].push(item);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -446,18 +376,12 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|||||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||||
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||||
|
|
||||||
const graphicItem = mergedContentSequence.find(
|
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD');
|
||||||
group => group.ValueType === 'SCOORD'
|
|
||||||
);
|
|
||||||
|
|
||||||
const UIDREFContentItem = mergedContentSequence.find(
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||||
group => group.ValueType === 'UIDREF'
|
|
||||||
);
|
|
||||||
|
|
||||||
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||||
item =>
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.TrackingIdentifier
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!graphicItem) {
|
if (!graphicItem) {
|
||||||
@ -467,9 +391,7 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUMContentItems = mergedContentSequence.filter(
|
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||||
group => group.ValueType === 'NUM'
|
|
||||||
);
|
|
||||||
|
|
||||||
const measurement = {
|
const measurement = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
@ -484,10 +406,7 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|||||||
|
|
||||||
if (MeasuredValueSequence) {
|
if (MeasuredValueSequence) {
|
||||||
measurement.labels.push(
|
measurement.labels.push(
|
||||||
_getLabelFromMeasuredValueSequence(
|
_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence)
|
||||||
ConceptNameCodeSequence,
|
|
||||||
MeasuredValueSequence
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -496,32 +415,22 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||||
const NUMContentItems = mergedContentSequence.filter(
|
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||||
group => group.ValueType === 'NUM'
|
|
||||||
);
|
|
||||||
|
|
||||||
const UIDREFContentItem = mergedContentSequence.find(
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||||
group => group.ValueType === 'UIDREF'
|
|
||||||
);
|
|
||||||
|
|
||||||
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||||
item =>
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.TrackingIdentifier
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const finding = mergedContentSequence.find(
|
const finding = mergedContentSequence.find(
|
||||||
item =>
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.Finding
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const findingSites = mergedContentSequence.filter(
|
const findingSites = mergedContentSequence.filter(
|
||||||
item =>
|
item =>
|
||||||
item.ConceptNameCodeSequence.CodingSchemeDesignator ===
|
item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT &&
|
||||||
CodingSchemeDesignators.SRT &&
|
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.FindingSite
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const measurement = {
|
const measurement = {
|
||||||
@ -537,8 +446,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||||
finding.ConceptCodeSequence.CodingSchemeDesignator
|
finding.ConceptCodeSequence.CodingSchemeDesignator
|
||||||
) &&
|
) &&
|
||||||
finding.ConceptCodeSequence.CodeValue ===
|
finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||||
CodeNameCodeSequenceValues.CornerstoneFreeText
|
|
||||||
) {
|
) {
|
||||||
measurement.labels.push({
|
measurement.labels.push({
|
||||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||||
@ -553,8 +461,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||||
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
||||||
) &&
|
) &&
|
||||||
FindingSite.ConceptCodeSequence.CodeValue ===
|
FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||||
CodeNameCodeSequenceValues.CornerstoneFreeText
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cornerstoneFreeTextFindingSite) {
|
if (cornerstoneFreeTextFindingSite) {
|
||||||
@ -566,18 +473,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NUMContentItems.forEach(item => {
|
NUMContentItems.forEach(item => {
|
||||||
const {
|
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
|
||||||
ConceptNameCodeSequence,
|
|
||||||
ContentSequence,
|
|
||||||
MeasuredValueSequence,
|
|
||||||
} = item;
|
|
||||||
|
|
||||||
const { ValueType } = ContentSequence;
|
const { ValueType } = ContentSequence;
|
||||||
|
|
||||||
if (!ValueType === 'SCOORD') {
|
if (!ValueType === 'SCOORD') {
|
||||||
console.warn(
|
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
||||||
`Graphic ${ValueType} not currently supported, skipping annotation.`
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -590,10 +491,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|||||||
|
|
||||||
if (MeasuredValueSequence) {
|
if (MeasuredValueSequence) {
|
||||||
measurement.labels.push(
|
measurement.labels.push(
|
||||||
_getLabelFromMeasuredValueSequence(
|
_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence)
|
||||||
ConceptNameCodeSequence,
|
|
||||||
MeasuredValueSequence
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -633,17 +531,12 @@ function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
|||||||
return coords;
|
return coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getLabelFromMeasuredValueSequence(
|
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
||||||
ConceptNameCodeSequence,
|
|
||||||
MeasuredValueSequence
|
|
||||||
) {
|
|
||||||
const { CodeMeaning } = ConceptNameCodeSequence;
|
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||||
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||||
|
|
||||||
const formatedNumericValue = NumericValue
|
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
||||||
? Number(NumericValue).toFixed(2)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: CodeMeaning,
|
label: CodeMeaning,
|
||||||
@ -653,24 +546,20 @@ function _getLabelFromMeasuredValueSequence(
|
|||||||
|
|
||||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||||
item =>
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.ImageLibrary
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ImageLibraryGroup = _getSequenceAsArray(
|
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
||||||
ImageLibrary.ContentSequence
|
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||||
).find(
|
|
||||||
item =>
|
|
||||||
item.ConceptNameCodeSequence.CodeValue ===
|
|
||||||
CodeNameCodeSequenceValues.ImageLibraryGroup
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const referencedImages = [];
|
const referencedImages = [];
|
||||||
|
|
||||||
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
|
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
|
||||||
const { ReferencedSOPSequence } = item;
|
const { ReferencedSOPSequence } = item;
|
||||||
if (!ReferencedSOPSequence) return;
|
if (!ReferencedSOPSequence) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const ref of _getSequenceAsArray(ReferencedSOPSequence)) {
|
for (const ref of _getSequenceAsArray(ReferencedSOPSequence)) {
|
||||||
if (ref.ReferencedSOPClassUID) {
|
if (ref.ReferencedSOPClassUID) {
|
||||||
const { ReferencedSOPClassUID, ReferencedSOPInstanceUID } = ref;
|
const { ReferencedSOPClassUID, ReferencedSOPInstanceUID } = ref;
|
||||||
@ -687,7 +576,9 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _getSequenceAsArray(sequence) {
|
function _getSequenceAsArray(sequence) {
|
||||||
if (!sequence) return [];
|
if (!sequence) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return Array.isArray(sequence) ? sequence : [sequence];
|
return Array.isArray(sequence) ? sequence : [sequence];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
import getHangingProtocolModule, {
|
import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule';
|
||||||
srProtocol,
|
|
||||||
} from './getHangingProtocolModule';
|
|
||||||
import onModeEnter from './onModeEnter';
|
import onModeEnter from './onModeEnter';
|
||||||
import getCommandsModule from './commandsModule';
|
import getCommandsModule from './commandsModule';
|
||||||
import preRegistration from './init';
|
import preRegistration from './init';
|
||||||
@ -12,9 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
|||||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import(
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport');
|
||||||
/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const OHIFCornerstoneSRViewport = props => {
|
const OHIFCornerstoneSRViewport = props => {
|
||||||
@ -58,7 +54,7 @@ const dicomSRExtension = {
|
|||||||
},
|
},
|
||||||
getCommandsModule,
|
getCommandsModule,
|
||||||
getSopClassHandlerModule,
|
getSopClassHandlerModule,
|
||||||
// Include dynmically computed values such as toolNames not known till instantiation
|
// Include dynamically computed values such as toolNames not known till instantiation
|
||||||
getUtilityModule({ servicesManager }) {
|
getUtilityModule({ servicesManager }) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -18,9 +18,7 @@ import toolNames from './tools/toolNames';
|
|||||||
/**
|
/**
|
||||||
* @param {object} configuration
|
* @param {object} configuration
|
||||||
*/
|
*/
|
||||||
export default function init({
|
export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void {
|
||||||
configuration = {},
|
|
||||||
}: Types.Extensions.ExtensionParams): void {
|
|
||||||
addTool(DICOMSRDisplayTool);
|
addTool(DICOMSRDisplayTool);
|
||||||
addToolInstance(toolNames.SRLength, LengthTool, {});
|
addToolInstance(toolNames.SRLength, LengthTool, {});
|
||||||
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
||||||
|
|||||||
@ -40,49 +40,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
isPointNearTool = () => null;
|
isPointNearTool = () => null;
|
||||||
getHandleNearImagePoint = () => null;
|
getHandleNearImagePoint = () => null;
|
||||||
|
|
||||||
renderAnnotation = (
|
renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => {
|
||||||
enabledElement: Types.IEnabledElement,
|
|
||||||
svgDrawingHelper: any
|
|
||||||
): void => {
|
|
||||||
const { viewport } = enabledElement;
|
const { viewport } = enabledElement;
|
||||||
const { element } = viewport;
|
const { element } = viewport;
|
||||||
|
|
||||||
let annotations = annotation.state.getAnnotations(
|
let annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
||||||
this.getToolName(),
|
|
||||||
element
|
|
||||||
);
|
|
||||||
|
|
||||||
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
||||||
if (!annotations?.length) {
|
if (!annotations?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations = this.filterInteractableAnnotationsForElement(
|
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
||||||
element,
|
|
||||||
annotations
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!annotations?.length) {
|
if (!annotations?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement(
|
const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement(element);
|
||||||
element
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const { activeIndex, trackingUniqueIdentifiers } = trackingUniqueIdentifiersForElement;
|
||||||
activeIndex,
|
|
||||||
trackingUniqueIdentifiers,
|
|
||||||
} = trackingUniqueIdentifiersForElement;
|
|
||||||
|
|
||||||
const activeTrackingUniqueIdentifier =
|
const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex];
|
||||||
trackingUniqueIdentifiers[activeIndex];
|
|
||||||
|
|
||||||
// Filter toolData to only render the data for the active SR.
|
// Filter toolData to only render the data for the active SR.
|
||||||
const filteredAnnotations = annotations.filter(annotation =>
|
const filteredAnnotations = annotations.filter(annotation =>
|
||||||
trackingUniqueIdentifiers.includes(
|
trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier)
|
||||||
annotation.data?.cachedStats?.TrackingUniqueIdentifier
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!viewport._actors?.size) {
|
if (!viewport._actors?.size) {
|
||||||
@ -138,8 +121,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
break;
|
break;
|
||||||
case SCOORD_TYPES.ELLIPSE:
|
case SCOORD_TYPES.ELLIPSE:
|
||||||
renderMethod = this.renderEllipse;
|
renderMethod = this.renderEllipse;
|
||||||
canvasCoordinatesAdapter =
|
canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners;
|
||||||
utilities.math.ellipse.getCanvasEllipseCorners;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported GraphicType: ${GraphicType}`);
|
throw new Error(`Unsupported GraphicType: ${GraphicType}`);
|
||||||
@ -221,15 +203,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
renderableData.map((data, index) => {
|
renderableData.map((data, index) => {
|
||||||
canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
||||||
const handleGroupUID = '0';
|
const handleGroupUID = '0';
|
||||||
drawing.drawHandles(
|
drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, {
|
||||||
svgDrawingHelper,
|
color: options.color,
|
||||||
annotationUID,
|
});
|
||||||
handleGroupUID,
|
|
||||||
canvasCoordinates,
|
|
||||||
{
|
|
||||||
color: options.color,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,10 +224,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
canvasCoordinates.push(viewport.worldToCanvas(point));
|
canvasCoordinates.push(viewport.worldToCanvas(point));
|
||||||
|
|
||||||
// We get the other point for the arrow by using the image size
|
// We get the other point for the arrow by using the image size
|
||||||
const imagePixelModule = metaData.get(
|
const imagePixelModule = metaData.get('imagePixelModule', referencedImageId);
|
||||||
'imagePixelModule',
|
|
||||||
referencedImageId
|
|
||||||
);
|
|
||||||
|
|
||||||
let xOffset = 10;
|
let xOffset = 10;
|
||||||
let yOffset = 10;
|
let yOffset = 10;
|
||||||
@ -309,23 +282,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
|
|
||||||
const rotation = viewport.getRotation();
|
const rotation = viewport.getRotation();
|
||||||
|
|
||||||
canvasCoordinates = ellipsePointsWorld.map(p =>
|
canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p));
|
||||||
viewport.worldToCanvas(p)
|
|
||||||
);
|
|
||||||
let canvasCorners;
|
let canvasCorners;
|
||||||
if (rotation == 90 || rotation == 270) {
|
if (rotation == 90 || rotation == 270) {
|
||||||
canvasCorners = <Array<Types.Point2>>(
|
canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners([
|
||||||
utilities.math.ellipse.getCanvasEllipseCorners([
|
canvasCoordinates[2],
|
||||||
canvasCoordinates[2],
|
canvasCoordinates[3],
|
||||||
canvasCoordinates[3],
|
canvasCoordinates[0],
|
||||||
canvasCoordinates[0],
|
canvasCoordinates[1],
|
||||||
canvasCoordinates[1],
|
]) as Array<Types.Point2>;
|
||||||
])
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
canvasCorners = <Array<Types.Point2>>(
|
canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners(
|
||||||
utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates)
|
canvasCoordinates
|
||||||
);
|
) as Array<Types.Point2>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineUID = `${index}`;
|
const lineUID = `${index}`;
|
||||||
@ -368,23 +337,14 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
||||||
}
|
}
|
||||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
const textLines = this._getTextBoxLinesFromLabels(label);
|
||||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(
|
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||||
adaptedCanvasCoordinates
|
|
||||||
);
|
|
||||||
|
|
||||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(
|
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||||
canvasTextBoxCoords
|
|
||||||
);
|
|
||||||
|
|
||||||
const textBoxPosition = viewport.worldToCanvas(
|
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
||||||
annotation.data.handles.textBox.worldPosition
|
|
||||||
);
|
|
||||||
|
|
||||||
const textBoxUID = '1';
|
const textBoxUID = '1';
|
||||||
const textBoxOptions = this.getLinkedTextBoxStyle(
|
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
||||||
styleSpecifier,
|
|
||||||
annotation
|
|
||||||
);
|
|
||||||
|
|
||||||
const boundingBox = drawing.drawLinkedTextBox(
|
const boundingBox = drawing.drawLinkedTextBox(
|
||||||
svgDrawingHelper,
|
svgDrawingHelper,
|
||||||
|
|||||||
@ -27,15 +27,11 @@ function setTrackingUniqueIdentifiersForElement(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActiveTrackingUniqueIdentifierForElement(
|
function setActiveTrackingUniqueIdentifierForElement(element, TrackingUniqueIdentifier) {
|
||||||
element,
|
|
||||||
TrackingUniqueIdentifier
|
|
||||||
) {
|
|
||||||
const enabledElement = getEnabledElement(element);
|
const enabledElement = getEnabledElement(element);
|
||||||
const { viewport } = enabledElement;
|
const { viewport } = enabledElement;
|
||||||
|
|
||||||
const trackingIdentifiersForElement =
|
const trackingIdentifiersForElement = state.trackingIdentifiersByViewportId[viewport.id];
|
||||||
state.trackingIdentifiersByViewportId[viewport.id];
|
|
||||||
|
|
||||||
if (trackingIdentifiersForElement) {
|
if (trackingIdentifiersForElement) {
|
||||||
const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex(
|
const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex(
|
||||||
|
|||||||
@ -8,11 +8,7 @@ const EPSILON = 1e-4;
|
|||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||||
|
|
||||||
export default function addMeasurement(
|
export default function addMeasurement(measurement, imageId, displaySetInstanceUID) {
|
||||||
measurement,
|
|
||||||
imageId,
|
|
||||||
displaySetInstanceUID
|
|
||||||
) {
|
|
||||||
// TODO -> Render rotated ellipse .
|
// TODO -> Render rotated ellipse .
|
||||||
const toolName = toolNames.DICOMSRDisplay;
|
const toolName = toolNames.DICOMSRDisplay;
|
||||||
|
|
||||||
@ -31,12 +27,7 @@ export default function addMeasurement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
measurementData.renderableData[GraphicType].push(
|
measurementData.renderableData[GraphicType].push(
|
||||||
_getRenderableData(
|
_getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier)
|
||||||
GraphicType,
|
|
||||||
GraphicData,
|
|
||||||
imageId,
|
|
||||||
measurement.TrackingIdentifier
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,12 +77,7 @@ export default function addMeasurement(
|
|||||||
delete measurement.coords;
|
delete measurement.coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getRenderableData(
|
function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) {
|
||||||
GraphicType,
|
|
||||||
GraphicData,
|
|
||||||
imageId,
|
|
||||||
TrackingIdentifier
|
|
||||||
) {
|
|
||||||
const [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
const [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
||||||
|
|
||||||
let renderableData: csTypes.Point3[];
|
let renderableData: csTypes.Point3[];
|
||||||
@ -207,38 +193,22 @@ function _getRenderableData(
|
|||||||
throw new Error('imageId does not have imagePlaneModule metadata');
|
throw new Error('imageId does not have imagePlaneModule metadata');
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { columnCosines }: { columnCosines: csTypes.Point3 } = imagePlaneModule;
|
||||||
columnCosines,
|
|
||||||
}: { columnCosines: csTypes.Point3 } = imagePlaneModule;
|
|
||||||
|
|
||||||
// find which axis is parallel to the columnCosines
|
// find which axis is parallel to the columnCosines
|
||||||
const columnCosinesVec = vec3.fromValues(...columnCosines);
|
const columnCosinesVec = vec3.fromValues(...columnCosines);
|
||||||
|
|
||||||
const projectedMajorAxisOnColVec = Math.abs(
|
const projectedMajorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, majorAxisVec));
|
||||||
vec3.dot(columnCosinesVec, majorAxisVec)
|
const projectedMinorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, minorAxisVec));
|
||||||
);
|
|
||||||
const projectedMinorAxisOnColVec = Math.abs(
|
|
||||||
vec3.dot(columnCosinesVec, minorAxisVec)
|
|
||||||
);
|
|
||||||
|
|
||||||
const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
|
const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
|
||||||
const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
|
const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
|
||||||
|
|
||||||
renderableData = [];
|
renderableData = [];
|
||||||
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
||||||
renderableData = [
|
renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]];
|
||||||
pointsWorld[0],
|
|
||||||
pointsWorld[1],
|
|
||||||
pointsWorld[2],
|
|
||||||
pointsWorld[3],
|
|
||||||
];
|
|
||||||
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
||||||
renderableData = [
|
renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]];
|
||||||
pointsWorld[2],
|
|
||||||
pointsWorld[3],
|
|
||||||
pointsWorld[0],
|
|
||||||
pointsWorld[1],
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
|
console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import { addTool } from '@cornerstonejs/tools';
|
import { addTool } from '@cornerstonejs/tools';
|
||||||
|
|
||||||
export default function addToolInstance(
|
export default function addToolInstance(name: string, toolClass, configuration?): void {
|
||||||
name: string,
|
|
||||||
toolClass,
|
|
||||||
configuration?
|
|
||||||
): void {
|
|
||||||
class InstanceClass extends toolClass {
|
class InstanceClass extends toolClass {
|
||||||
static toolName = name;
|
static toolName = name;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,11 @@ import { DisplaySetService, classes } from '@ohif/core';
|
|||||||
const ImageSet = classes.ImageSet;
|
const ImageSet = classes.ImageSet;
|
||||||
|
|
||||||
const findInstance = (measurement, displaySetService: DisplaySetService) => {
|
const findInstance = (measurement, displaySetService: DisplaySetService) => {
|
||||||
const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } =
|
const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = measurement;
|
||||||
measurement;
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
if (!referencedDisplaySet.images) {
|
||||||
displaySetInstanceUID
|
return;
|
||||||
);
|
}
|
||||||
if (!referencedDisplaySet.images) return;
|
|
||||||
return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid);
|
return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -16,16 +15,17 @@ const findInstance = (measurement, displaySetService: DisplaySetService) => {
|
|||||||
* contained within the provided display set.
|
* contained within the provided display set.
|
||||||
* @return an array of instances referenced.
|
* @return an array of instances referenced.
|
||||||
*/
|
*/
|
||||||
const findReferencedInstances = (
|
const findReferencedInstances = (displaySetService: DisplaySetService, displaySet) => {
|
||||||
displaySetService: DisplaySetService,
|
|
||||||
displaySet
|
|
||||||
) => {
|
|
||||||
const instances = [];
|
const instances = [];
|
||||||
const instanceById = {};
|
const instanceById = {};
|
||||||
for (const measurement of displaySet.measurements) {
|
for (const measurement of displaySet.measurements) {
|
||||||
const { imageId } = measurement;
|
const { imageId } = measurement;
|
||||||
if (!imageId) continue;
|
if (!imageId) {
|
||||||
if (instanceById[imageId]) continue;
|
continue;
|
||||||
|
}
|
||||||
|
if (instanceById[imageId]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const instance = findInstance(measurement, displaySetService);
|
const instance = findInstance(measurement, displaySetService);
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
@ -50,7 +50,7 @@ const findReferencedInstances = (
|
|||||||
const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
||||||
const instances = findReferencedInstances(displaySetService, displaySet);
|
const instances = findReferencedInstances(displaySetService, displaySet);
|
||||||
// This will be a member function of the created image set
|
// This will be a member function of the created image set
|
||||||
const updateInstances = function() {
|
const updateInstances = function () {
|
||||||
this.images.splice(
|
this.images.splice(
|
||||||
0,
|
0,
|
||||||
this.images.length,
|
this.images.length,
|
||||||
|
|||||||
@ -9,11 +9,12 @@ const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => {
|
|||||||
let instanceFound;
|
let instanceFound;
|
||||||
|
|
||||||
displaySets.find(displaySet => {
|
displaySets.find(displaySet => {
|
||||||
if (!displaySet.images) return false;
|
if (!displaySet.images) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
instanceFound = displaySet.images.find(
|
instanceFound = displaySet.images.find(
|
||||||
instanceMetadata =>
|
instanceMetadata => instanceMetadata.getSOPInstanceUID() === SOPInstanceUID
|
||||||
instanceMetadata.getSOPInstanceUID() === SOPInstanceUID
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return !!instanceFound;
|
return !!instanceFound;
|
||||||
|
|||||||
@ -18,10 +18,7 @@ const findMostRecentStructuredReport = studies => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isStructuredReportSeries(series)) {
|
if (isStructuredReportSeries(series)) {
|
||||||
if (
|
if (!mostRecentStructuredReport || compareSeriesDate(series, mostRecentStructuredReport)) {
|
||||||
!mostRecentStructuredReport ||
|
|
||||||
compareSeriesDate(series, mostRecentStructuredReport)
|
|
||||||
) {
|
|
||||||
mostRecentStructuredReport = series;
|
mostRecentStructuredReport = series;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,10 +35,7 @@ const findMostRecentStructuredReport = studies => {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
const isStructuredReportSeries = series => {
|
const isStructuredReportSeries = series => {
|
||||||
const supportedSopClassUIDs = [
|
const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22', '1.2.840.10008.5.1.4.1.1.11.1'];
|
||||||
'1.2.840.10008.5.1.4.1.1.88.22',
|
|
||||||
'1.2.840.10008.5.1.4.1.1.11.1',
|
|
||||||
];
|
|
||||||
|
|
||||||
const firstInstance = series.getFirstInstance();
|
const firstInstance = series.getFirstInstance();
|
||||||
const SOPClassUID = firstInstance.getData().metadata.SOPClassUID;
|
const SOPClassUID = firstInstance.getData().metadata.SOPClassUID;
|
||||||
@ -50,7 +44,7 @@ const isStructuredReportSeries = series => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkes if series1 is newer than series2
|
* Checks if series1 is newer than series2
|
||||||
*
|
*
|
||||||
* @param {Object} series1 - Series Metadata 1
|
* @param {Object} series1 - Series Metadata 1
|
||||||
* @param {Object} series2 - Series Metadata 2
|
* @param {Object} series2 - Series Metadata 2
|
||||||
|
|||||||
@ -2,17 +2,12 @@ import OHIF from '@ohif/core';
|
|||||||
import { annotation } from '@cornerstonejs/tools';
|
import { annotation } from '@cornerstonejs/tools';
|
||||||
const { log } = OHIF;
|
const { log } = OHIF;
|
||||||
|
|
||||||
function getFilteredCornerstoneToolState(
|
function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes) {
|
||||||
measurementData,
|
|
||||||
additionalFindingTypes
|
|
||||||
) {
|
|
||||||
const filteredToolState = {};
|
const filteredToolState = {};
|
||||||
|
|
||||||
function addToFilteredToolState(annotation, toolType) {
|
function addToFilteredToolState(annotation, toolType) {
|
||||||
if (!annotation.metadata?.referencedImageId) {
|
if (!annotation.metadata?.referencedImageId) {
|
||||||
log.warn(
|
log.warn(`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`);
|
||||||
`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,9 +25,7 @@ function getFilteredCornerstoneToolState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const measurementDataI = measurementData.find(
|
const measurementDataI = measurementData.find(md => md.uid === annotation.annotationUID);
|
||||||
md => md.uid === annotation.annotationUID
|
|
||||||
);
|
|
||||||
const toolData = imageIdSpecificToolState[toolType].data;
|
const toolData = imageIdSpecificToolState[toolType].data;
|
||||||
|
|
||||||
let { finding } = measurementDataI;
|
let { finding } = measurementDataI;
|
||||||
@ -77,9 +70,7 @@ function getFilteredCornerstoneToolState(
|
|||||||
for (let i = 0; i < framesOfReference.length; i++) {
|
for (let i = 0; i < framesOfReference.length; i++) {
|
||||||
const frameOfReference = framesOfReference[i];
|
const frameOfReference = framesOfReference[i];
|
||||||
|
|
||||||
const frameOfReferenceAnnotations = annotationManager.getAnnotations(
|
const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference);
|
||||||
frameOfReference
|
|
||||||
);
|
|
||||||
|
|
||||||
const toolTypes = Object.keys(frameOfReferenceAnnotations);
|
const toolTypes = Object.keys(frameOfReferenceAnnotations);
|
||||||
|
|
||||||
@ -91,9 +82,7 @@ function getFilteredCornerstoneToolState(
|
|||||||
if (annotations) {
|
if (annotations) {
|
||||||
for (let k = 0; k < annotations.length; k++) {
|
for (let k = 0; k < annotations.length; k++) {
|
||||||
const annotation = annotations[k];
|
const annotation = annotations[k];
|
||||||
const uidIndex = uids.findIndex(
|
const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID);
|
||||||
uid => uid === annotation.annotationUID
|
|
||||||
);
|
|
||||||
|
|
||||||
if (uidIndex !== -1) {
|
if (uidIndex !== -1) {
|
||||||
addToFilteredToolState(annotation, toolType);
|
addToFilteredToolState(annotation, toolType);
|
||||||
|
|||||||
@ -9,9 +9,7 @@
|
|||||||
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
||||||
const { findingSites = [], finding } = toolData;
|
const { findingSites = [], finding } = toolData;
|
||||||
|
|
||||||
let freeTextLabel = findingSites.find(
|
let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT');
|
||||||
fs => fs.CodeValue === 'CORNERSTONEFREETEXT'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (freeTextLabel) {
|
if (freeTextLabel) {
|
||||||
return freeTextLabel.CodeMeaning;
|
return freeTextLabel.CodeMeaning;
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { utilities, metaData } from '@cornerstonejs/core';
|
|||||||
import OHIF, { DicomMetadataStore } from '@ohif/core';
|
import OHIF, { DicomMetadataStore } from '@ohif/core';
|
||||||
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
|
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
|
||||||
|
const { locking } = CsAnnotation;
|
||||||
|
|
||||||
const { guid } = OHIF.utils;
|
const { guid } = OHIF.utils;
|
||||||
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
||||||
@ -12,20 +14,26 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||||
|
|
||||||
const convertCode = (codingValues, code) => {
|
const convertCode = (codingValues, code) => {
|
||||||
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
|
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
|
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
|
||||||
const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning };
|
const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning };
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertSites = (codingValues, sites) => {
|
const convertSites = (codingValues, sites) => {
|
||||||
if (!sites || !sites.length) return;
|
if (!sites || !sites.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ret = [];
|
const ret = [];
|
||||||
// Do as a loop to convert away from Proxy instances
|
// Do as a loop to convert away from Proxy instances
|
||||||
for (let i = 0; i < sites.length; i++) {
|
for (let i = 0; i < sites.length; i++) {
|
||||||
// Deal with irregular conversion from dcmjs
|
// Deal with irregular conversion from dcmjs
|
||||||
const site = convertCode(codingValues, sites[i][0] || sites[i]);
|
const site = convertCode(codingValues, sites[i][0] || sites[i]);
|
||||||
if (site) ret.push(site);
|
if (site) {
|
||||||
|
ret.push(site);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (ret.length && ret) || undefined;
|
return (ret.length && ret) || undefined;
|
||||||
};
|
};
|
||||||
@ -35,23 +43,16 @@ const convertSites = (codingValues, sites) => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default function hydrateStructuredReport(
|
export default function hydrateStructuredReport(
|
||||||
{ servicesManager, extensionManager },
|
{ servicesManager, extensionManager, appConfig },
|
||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
|
const annotationManager = CsAnnotation.state.getAnnotationManager();
|
||||||
|
const disableEditing = appConfig?.disableEditing;
|
||||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||||
const {
|
const { measurementService, displaySetService, customizationService } = servicesManager.services;
|
||||||
measurementService,
|
|
||||||
displaySetService,
|
|
||||||
customizationService,
|
|
||||||
} = servicesManager.services;
|
|
||||||
|
|
||||||
const codingValues = customizationService.getCustomization(
|
const codingValues = customizationService.getCustomization('codingValues', {});
|
||||||
'codingValues',
|
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
{}
|
|
||||||
);
|
|
||||||
const displaySet = displaySetService.getDisplaySetByUID(
|
|
||||||
displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO -> We should define a strict versioning somewhere.
|
// TODO -> We should define a strict versioning somewhere.
|
||||||
const mappings = measurementService.getSourceMappings(
|
const mappings = measurementService.getSourceMappings(
|
||||||
@ -115,16 +116,14 @@ export default function hydrateStructuredReport(
|
|||||||
|
|
||||||
// TODO: notification if no hydratable?
|
// TODO: notification if no hydratable?
|
||||||
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
||||||
const toolDataForAnnotationType =
|
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
|
||||||
hydratableMeasurementsInSR[annotationType];
|
|
||||||
|
|
||||||
toolDataForAnnotationType.forEach(toolData => {
|
toolDataForAnnotationType.forEach(toolData => {
|
||||||
// Add the measurement to toolState
|
// Add the measurement to toolState
|
||||||
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
||||||
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||||
// in the wrong value.
|
// in the wrong value.
|
||||||
const frameNumber =
|
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||||
(toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
|
||||||
const imageId =
|
const imageId =
|
||||||
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||||
@ -140,10 +139,7 @@ export default function hydrateStructuredReport(
|
|||||||
|
|
||||||
for (let i = 0; i < imageIds.length; i++) {
|
for (let i = 0; i < imageIds.length; i++) {
|
||||||
const imageId = imageIds[i];
|
const imageId = imageIds[i];
|
||||||
const { SeriesInstanceUID, StudyInstanceUID } = metaData.get(
|
const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId);
|
||||||
'instance',
|
|
||||||
imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) {
|
if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) {
|
||||||
SeriesInstanceUIDs.push(SeriesInstanceUID);
|
SeriesInstanceUIDs.push(SeriesInstanceUID);
|
||||||
@ -152,23 +148,19 @@ export default function hydrateStructuredReport(
|
|||||||
if (!targetStudyInstanceUID) {
|
if (!targetStudyInstanceUID) {
|
||||||
targetStudyInstanceUID = StudyInstanceUID;
|
targetStudyInstanceUID = StudyInstanceUID;
|
||||||
} else if (targetStudyInstanceUID !== StudyInstanceUID) {
|
} else if (targetStudyInstanceUID !== StudyInstanceUID) {
|
||||||
console.warn(
|
console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.');
|
||||||
'NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
||||||
const toolDataForAnnotationType =
|
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
|
||||||
hydratableMeasurementsInSR[annotationType];
|
|
||||||
|
|
||||||
toolDataForAnnotationType.forEach(toolData => {
|
toolDataForAnnotationType.forEach(toolData => {
|
||||||
// Add the measurement to toolState
|
// Add the measurement to toolState
|
||||||
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
||||||
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||||
// in the wrong value.
|
// in the wrong value.
|
||||||
const frameNumber =
|
const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||||
(toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
|
||||||
const imageId =
|
const imageId =
|
||||||
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||||
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||||
@ -198,21 +190,13 @@ export default function hydrateStructuredReport(
|
|||||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||||
);
|
);
|
||||||
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
||||||
annotation.data.finding = convertCode(
|
annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]);
|
||||||
codingValues,
|
annotation.data.findingSites = convertSites(codingValues, toolData.findingSites);
|
||||||
toolData.finding?.[0]
|
|
||||||
);
|
|
||||||
annotation.data.findingSites = convertSites(
|
|
||||||
codingValues,
|
|
||||||
toolData.findingSites
|
|
||||||
);
|
|
||||||
annotation.data.site = annotation.data.findingSites?.[0];
|
annotation.data.site = annotation.data.findingSites?.[0];
|
||||||
|
|
||||||
const matchingMapping = mappings.find(
|
const matchingMapping = mappings.find(m => m.annotationType === annotationType);
|
||||||
m => m.annotationType === annotationType
|
|
||||||
);
|
|
||||||
|
|
||||||
measurementService.addRawMeasurement(
|
const newAnnotationUID = measurementService.addRawMeasurement(
|
||||||
source,
|
source,
|
||||||
annotationType,
|
annotationType,
|
||||||
{ annotation },
|
{ annotation },
|
||||||
@ -220,6 +204,11 @@ export default function hydrateStructuredReport(
|
|||||||
dataSource
|
dataSource
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (disableEditing) {
|
||||||
|
const addedAnnotation = annotationManager.getAnnotation(newAnnotationUID);
|
||||||
|
locking.setAnnotationLocked(addedAnnotation, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (!imageIds.includes(imageId)) {
|
if (!imageIds.includes(imageId)) {
|
||||||
imageIds.push(imageId);
|
imageIds.push(imageId);
|
||||||
}
|
}
|
||||||
@ -245,15 +234,14 @@ function _mapLegacyDataSet(dataset) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Retrieve the Measurements themselves
|
// Retrieve the Measurements themselves
|
||||||
const measurementGroups = toArray(
|
const measurementGroups = toArray(imagingMeasurementContent.ContentSequence).filter(
|
||||||
imagingMeasurementContent.ContentSequence
|
codeMeaningEquals(GROUP)
|
||||||
).filter(codeMeaningEquals(GROUP));
|
);
|
||||||
|
|
||||||
// For each of the supported measurement types, compute the measurement data
|
// For each of the supported measurement types, compute the measurement data
|
||||||
const measurementData = {};
|
const measurementData = {};
|
||||||
|
|
||||||
const cornerstoneToolClasses =
|
const cornerstoneToolClasses = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||||
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
||||||
|
|
||||||
const registeredToolClasses = [];
|
const registeredToolClasses = [];
|
||||||
|
|
||||||
@ -263,13 +251,10 @@ function _mapLegacyDataSet(dataset) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
measurementGroups.forEach((measurementGroup, index) => {
|
measurementGroups.forEach((measurementGroup, index) => {
|
||||||
const measurementGroupContentSequence = toArray(
|
const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence);
|
||||||
measurementGroup.ContentSequence
|
|
||||||
);
|
|
||||||
|
|
||||||
const TrackingIdentifierGroup = measurementGroupContentSequence.find(
|
const TrackingIdentifierGroup = measurementGroupContentSequence.find(
|
||||||
contentItem =>
|
contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER
|
||||||
contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const TrackingIdentifier = TrackingIdentifierGroup.TextValue;
|
const TrackingIdentifier = TrackingIdentifierGroup.TextValue;
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
|
|
||||||
const cornerstoneAdapters =
|
const cornerstoneAdapters =
|
||||||
adaptersSR.Cornerstone3D.MeasurementReport
|
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||||
.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||||
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
|
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
|
||||||
@ -24,8 +23,7 @@ export default function isRehydratable(displaySet, mappings) {
|
|||||||
|
|
||||||
const adapterKeys = Object.keys(cornerstoneAdapters).filter(
|
const adapterKeys = Object.keys(cornerstoneAdapters).filter(
|
||||||
adapterKey =>
|
adapterKey =>
|
||||||
typeof cornerstoneAdapters[adapterKey]
|
typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function'
|
||||||
.isValidCornerstoneTrackingIdentifier === 'function'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const adapters = [];
|
const adapters = [];
|
||||||
@ -48,19 +46,13 @@ export default function isRehydratable(displaySet, mappings) {
|
|||||||
|
|
||||||
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
|
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
|
||||||
|
|
||||||
return adapter.isValidCornerstoneTrackingIdentifier(
|
return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier);
|
||||||
mappedTrackingIdentifier
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hydratable) {
|
if (hydratable) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
console.log(
|
console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]);
|
||||||
'Measurement is not rehydratable',
|
|
||||||
TrackingIdentifier,
|
|
||||||
measurements[i]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('No measurements found which were rehydratable');
|
console.log('No measurements found which were rehydratable');
|
||||||
|
|||||||
@ -7,11 +7,11 @@ import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSR
|
|||||||
|
|
||||||
import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui';
|
import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui';
|
||||||
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
||||||
|
import { useAppConfig } from '@state';
|
||||||
|
|
||||||
const { formatDate } = utils;
|
const { formatDate } = utils;
|
||||||
|
|
||||||
const MEASUREMENT_TRACKING_EXTENSION_ID =
|
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
|
||||||
'@ohif/extension-measurement-tracking';
|
|
||||||
|
|
||||||
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
||||||
|
|
||||||
@ -20,18 +20,18 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
children,
|
children,
|
||||||
dataSource,
|
dataSource,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportIndex,
|
|
||||||
viewportLabel,
|
viewportLabel,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
extensionManager,
|
extensionManager,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const [appConfig] = useAppConfig();
|
||||||
displaySetService,
|
|
||||||
cornerstoneViewportService,
|
const { displaySetService, cornerstoneViewportService, measurementService } =
|
||||||
measurementService,
|
servicesManager.services;
|
||||||
} = servicesManager.services;
|
|
||||||
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
|
||||||
// SR viewport will always have a single display set
|
// SR viewport will always have a single display set
|
||||||
if (displaySets.length > 1) {
|
if (displaySets.length > 1) {
|
||||||
@ -43,15 +43,10 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||||
const [measurementSelected, setMeasurementSelected] = useState(0);
|
const [measurementSelected, setMeasurementSelected] = useState(0);
|
||||||
const [measurementCount, setMeasurementCount] = useState(1);
|
const [measurementCount, setMeasurementCount] = useState(1);
|
||||||
const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState(
|
const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState(null);
|
||||||
null
|
const [referencedDisplaySetMetadata, setReferencedDisplaySetMetadata] = useState(null);
|
||||||
);
|
|
||||||
const [
|
|
||||||
referencedDisplaySetMetadata,
|
|
||||||
setReferencedDisplaySetMetadata,
|
|
||||||
] = useState(null);
|
|
||||||
const [element, setElement] = useState(null);
|
const [element, setElement] = useState(null);
|
||||||
const { viewports, activeViewportIndex } = viewportGrid;
|
const { viewports, activeViewportId } = viewportGrid;
|
||||||
|
|
||||||
// Optional hook into tracking extension, if present.
|
// Optional hook into tracking extension, if present.
|
||||||
let trackedMeasurements;
|
let trackedMeasurements;
|
||||||
@ -76,16 +71,14 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => {
|
sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => {
|
||||||
measurementService.clearMeasurements();
|
measurementService.clearMeasurements();
|
||||||
const { SeriesInstanceUIDs } = hydrateStructuredReport(
|
const { SeriesInstanceUIDs } = hydrateStructuredReport(
|
||||||
{ servicesManager, extensionManager },
|
{ servicesManager, extensionManager, appConfig },
|
||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
const displaySets = displaySetService.getDisplaySetsForSeries(
|
const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
|
||||||
SeriesInstanceUIDs[0]
|
|
||||||
);
|
|
||||||
if (displaySets.length) {
|
if (displaySets.length) {
|
||||||
viewportGridService.setDisplaySetsForViewports([
|
viewportGridService.setDisplaySetsForViewports([
|
||||||
{
|
{
|
||||||
viewportIndex: activeViewportIndex,
|
viewportId: activeViewportId,
|
||||||
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
|
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -123,11 +116,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
|
|
||||||
const updateViewport = useCallback(
|
const updateViewport = useCallback(
|
||||||
newMeasurementSelected => {
|
newMeasurementSelected => {
|
||||||
const {
|
const { StudyInstanceUID, displaySetInstanceUID, sopClassUids } = srDisplaySet;
|
||||||
StudyInstanceUID,
|
|
||||||
displaySetInstanceUID,
|
|
||||||
sopClassUids,
|
|
||||||
} = srDisplaySet;
|
|
||||||
|
|
||||||
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
||||||
return;
|
return;
|
||||||
@ -136,9 +125,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
if (sopClassUids && sopClassUids.length > 1) {
|
if (sopClassUids && sopClassUids.length > 1) {
|
||||||
// Todo: what happens if there are multiple SOP Classes? Why we are
|
// Todo: what happens if there are multiple SOP Classes? Why we are
|
||||||
// not throwing an error?
|
// not throwing an error?
|
||||||
console.warn(
|
console.warn('More than one SOPClassUID in the same series is not yet supported.');
|
||||||
'More than one SOPClassUID in the same series is not yet supported.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getViewportReferencedDisplaySetData(
|
_getViewportReferencedDisplaySetData(
|
||||||
@ -160,19 +147,11 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
// imageIdIndex will handle it by updating the viewport, but if they
|
// imageIdIndex will handle it by updating the viewport, but if they
|
||||||
// are the same we just need to use measurementService to jump to the
|
// are the same we just need to use measurementService to jump to the
|
||||||
// new measurement
|
// new measurement
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
const csViewport = cornerstoneViewportService.getCornerstoneViewport(
|
|
||||||
viewportInfo.getViewportId()
|
|
||||||
);
|
|
||||||
|
|
||||||
const imageIds = csViewport.getImageIds();
|
const imageIds = csViewport.getImageIds();
|
||||||
|
|
||||||
const imageIdIndex = imageIds.indexOf(
|
const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
|
||||||
measurements[newMeasurementSelected].imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imageIdIndex !== -1) {
|
if (imageIdIndex !== -1) {
|
||||||
csViewport.setImageIdIndex(imageIdIndex);
|
csViewport.setImageIdIndex(imageIdIndex);
|
||||||
@ -180,7 +159,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex]
|
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getCornerstoneViewport = useCallback(() => {
|
const getCornerstoneViewport = useCallback(() => {
|
||||||
@ -225,9 +204,10 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
}}
|
}}
|
||||||
onElementEnabled={onElementEnabled}
|
onElementEnabled={onElementEnabled}
|
||||||
initialImageIndex={initialImageIndex}
|
initialImageIndex={initialImageIndex}
|
||||||
|
isJumpToMeasurementDisabled={true}
|
||||||
></Component>
|
></Component>
|
||||||
);
|
);
|
||||||
}, [activeImageDisplaySetData, viewportIndex, measurementSelected]);
|
}, [activeImageDisplaySetData, viewportId, measurementSelected]);
|
||||||
|
|
||||||
const onMeasurementChange = useCallback(
|
const onMeasurementChange = useCallback(
|
||||||
direction => {
|
direction => {
|
||||||
@ -250,12 +230,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
setTrackingIdentifiers(newMeasurementSelected);
|
setTrackingIdentifiers(newMeasurementSelected);
|
||||||
updateViewport(newMeasurementSelected);
|
updateViewport(newMeasurementSelected);
|
||||||
},
|
},
|
||||||
[
|
[measurementSelected, measurementCount, updateViewport, setTrackingIdentifiers]
|
||||||
measurementSelected,
|
|
||||||
measurementCount,
|
|
||||||
updateViewport,
|
|
||||||
setTrackingIdentifiers,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,12 +240,10 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||||
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||||
({ displaySetInstanceUIDs }) => {
|
({ displaySetInstanceUIDs }) => {
|
||||||
const activeViewport = viewports[activeViewportIndex];
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
if (
|
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||||
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
|
|
||||||
) {
|
|
||||||
viewportGridService.setDisplaySetsForViewport({
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
viewportIndex: activeViewportIndex,
|
viewportId: activeViewportId,
|
||||||
displaySetInstanceUIDs: [],
|
displaySetInstanceUIDs: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -337,7 +310,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
return (
|
return (
|
||||||
child &&
|
child &&
|
||||||
React.cloneElement(child, {
|
React.cloneElement(child, {
|
||||||
viewportIndex,
|
viewportId,
|
||||||
key: index,
|
key: index,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -369,7 +342,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
getStatusComponent={() =>
|
getStatusComponent={() =>
|
||||||
_getStatusComponent({
|
_getStatusComponent({
|
||||||
srDisplaySet,
|
srDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
isTracked: false,
|
isTracked: false,
|
||||||
isRehydratable: srDisplaySet.isRehydratable,
|
isRehydratable: srDisplaySet.isRehydratable,
|
||||||
isLocked,
|
isLocked,
|
||||||
@ -383,23 +356,19 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
currentSeries: SeriesNumber,
|
currentSeries: SeriesNumber,
|
||||||
seriesDescription: SeriesDescription || '',
|
seriesDescription: SeriesDescription || '',
|
||||||
patientInformation: {
|
patientInformation: {
|
||||||
patientName: PatientName
|
patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '',
|
||||||
? OHIF.utils.formatPN(PatientName.Alphabetic)
|
|
||||||
: '',
|
|
||||||
patientSex: PatientSex || '',
|
patientSex: PatientSex || '',
|
||||||
patientAge: PatientAge || '',
|
patientAge: PatientAge || '',
|
||||||
MRN: PatientID || '',
|
MRN: PatientID || '',
|
||||||
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
|
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
|
||||||
spacing:
|
spacing:
|
||||||
SpacingBetweenSlices !== undefined
|
SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '',
|
||||||
? `${SpacingBetweenSlices.toFixed(2)}mm`
|
|
||||||
: '',
|
|
||||||
scanner: ManufacturerModelName || '',
|
scanner: ManufacturerModelName || '',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative flex flex-row w-full h-full overflow-hidden">
|
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||||
{getCornerstoneViewport()}
|
{getCornerstoneViewport()}
|
||||||
{childrenWithProps}
|
{childrenWithProps}
|
||||||
</div>
|
</div>
|
||||||
@ -409,7 +378,7 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
|
|
||||||
OHIFCornerstoneSRViewport.propTypes = {
|
OHIFCornerstoneSRViewport.propTypes = {
|
||||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
viewportIndex: PropTypes.number.isRequired,
|
viewportId: PropTypes.string.isRequired,
|
||||||
dataSource: PropTypes.object,
|
dataSource: PropTypes.object,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
viewportLabel: PropTypes.string,
|
viewportLabel: PropTypes.string,
|
||||||
@ -434,9 +403,7 @@ async function _getViewportReferencedDisplaySetData(
|
|||||||
|
|
||||||
const { displaySetInstanceUID } = measurement;
|
const { displaySetInstanceUID } = measurement;
|
||||||
|
|
||||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
const image0 = referencedDisplaySet.images[0];
|
const image0 = referencedDisplaySet.images[0];
|
||||||
const referencedDisplaySetMetadata = {
|
const referencedDisplaySetMetadata = {
|
||||||
@ -458,7 +425,7 @@ async function _getViewportReferencedDisplaySetData(
|
|||||||
|
|
||||||
function _getStatusComponent({
|
function _getStatusComponent({
|
||||||
srDisplaySet,
|
srDisplaySet,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
isRehydratable,
|
isRehydratable,
|
||||||
isLocked,
|
isLocked,
|
||||||
sendTrackedMeasurementsEvent,
|
sendTrackedMeasurementsEvent,
|
||||||
@ -466,7 +433,7 @@ function _getStatusComponent({
|
|||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
sendTrackedMeasurementsEvent('HYDRATE_SR', {
|
sendTrackedMeasurementsEvent('HYDRATE_SR', {
|
||||||
displaySetInstanceUID: srDisplaySet.displaySetInstanceUID,
|
displaySetInstanceUID: srDisplaySet.displaySetInstanceUID,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -476,8 +443,7 @@ function _getStatusComponent({
|
|||||||
// 1 - Incompatible
|
// 1 - Incompatible
|
||||||
// 2 - Locked
|
// 2 - Locked
|
||||||
// 3 - Rehydratable / Open
|
// 3 - Rehydratable / Open
|
||||||
const state =
|
const state = isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1;
|
||||||
isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1;
|
|
||||||
let ToolTipMessage = null;
|
let ToolTipMessage = null;
|
||||||
let StatusIcon = null;
|
let StatusIcon = null;
|
||||||
|
|
||||||
@ -507,22 +473,25 @@ function _getStatusComponent({
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
StatusIcon = () => <Icon name="status-untracked" />;
|
StatusIcon = () => (
|
||||||
|
<Icon
|
||||||
ToolTipMessage = () => (
|
className="text-aqua-pale"
|
||||||
<div>{`Click ${loadStr} to restore measurements.`}</div>
|
name="status-untracked"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ToolTipMessage = () => <div>{`Click ${loadStr} to restore measurements.`}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusArea = () => (
|
const StatusArea = () => (
|
||||||
<div className="flex h-6 leading-6 cursor-default text-sm text-white">
|
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
||||||
<div className="min-w-[45px] flex items-center p-1 rounded-l-xl rounded-r bg-customgray-100">
|
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
||||||
<StatusIcon />
|
<StatusIcon />
|
||||||
<span className="ml-1">SR</span>
|
<span className="ml-1">SR</span>
|
||||||
</div>
|
</div>
|
||||||
{state === 3 && (
|
{state === 3 && (
|
||||||
<div
|
<div
|
||||||
className="ml-1 px-1.5 rounded cursor-pointer hover:text-black bg-primary-main hover:bg-primary-light"
|
className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||||
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
// Using onMouseUp here because onClick is not working when the viewport is not active and is styled with pointer-events:none
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
>
|
>
|
||||||
@ -535,7 +504,10 @@ function _getStatusComponent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ToolTipMessage && (
|
{ToolTipMessage && (
|
||||||
<Tooltip content={<ToolTipMessage />} position="bottom-left">
|
<Tooltip
|
||||||
|
content={<ToolTipMessage />}
|
||||||
|
position="bottom-left"
|
||||||
|
>
|
||||||
<StatusArea />
|
<StatusArea />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -40,13 +40,7 @@ module.exports = (env, argv) => {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
filename: pkg.main,
|
filename: pkg.main,
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
/\b(vtk.js)/,
|
|
||||||
/\b(dcmjs)/,
|
|
||||||
/\b(gl-matrix)/,
|
|
||||||
/^@ohif/,
|
|
||||||
/^@cornerstonejs/,
|
|
||||||
],
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
maxChunks: 1,
|
maxChunks: 1,
|
||||||
|
|||||||
@ -0,0 +1,493 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8))
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "3.6.0",
|
"version": "3.7.0-beta.110",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -36,9 +36,9 @@
|
|||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||||
"@cornerstonejs/dicom-image-loader": "^0.6.8",
|
"@cornerstonejs/dicom-image-loader": "^1.20.3",
|
||||||
"@ohif/core": "3.6.0",
|
"@ohif/core": "3.7.0-beta.110",
|
||||||
"@ohif/ui": "3.6.0",
|
"@ohif/ui": "3.7.0-beta.110",
|
||||||
"dcmjs": "^0.29.6",
|
"dcmjs": "^0.29.6",
|
||||||
"dicom-parser": "^1.8.21",
|
"dicom-parser": "^1.8.21",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
@ -52,10 +52,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.1.0",
|
"@cornerstonejs/adapters": "^1.20.3",
|
||||||
"@cornerstonejs/core": "^1.1.0",
|
"@cornerstonejs/core": "^1.20.3",
|
||||||
"@cornerstonejs/streaming-image-volume-loader": "^1.1.0",
|
"@cornerstonejs/streaming-image-volume-loader": "^1.20.3",
|
||||||
"@cornerstonejs/tools": "^1.1.0",
|
"@cornerstonejs/tools": "^1.20.3",
|
||||||
"@kitware/vtk.js": "27.3.1",
|
"@kitware/vtk.js": "27.3.1",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
|
|||||||
@ -10,17 +10,8 @@ import {
|
|||||||
utilities as csUtils,
|
utilities as csUtils,
|
||||||
} from '@cornerstonejs/core';
|
} from '@cornerstonejs/core';
|
||||||
import { MeasurementService } from '@ohif/core';
|
import { MeasurementService } from '@ohif/core';
|
||||||
import {
|
import { Notification, useViewportDialog } from '@ohif/ui';
|
||||||
CinePlayer,
|
import { IStackViewport, IVolumeViewport } from '@cornerstonejs/core/dist/esm/types';
|
||||||
useCine,
|
|
||||||
useViewportGrid,
|
|
||||||
Notification,
|
|
||||||
useViewportDialog,
|
|
||||||
} from '@ohif/ui';
|
|
||||||
import {
|
|
||||||
IStackViewport,
|
|
||||||
IVolumeViewport,
|
|
||||||
} from '@cornerstonejs/core/dist/esm/types';
|
|
||||||
|
|
||||||
import { setEnabledElement } from '../state';
|
import { setEnabledElement } from '../state';
|
||||||
|
|
||||||
@ -28,6 +19,8 @@ import './OHIFCornerstoneViewport.css';
|
|||||||
import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
|
import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
|
||||||
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
import CornerstoneServices from '../types/CornerstoneServices';
|
import CornerstoneServices from '../types/CornerstoneServices';
|
||||||
|
import CinePlayer from '../components/CinePlayer';
|
||||||
|
import { Types } from '@ohif/core';
|
||||||
|
|
||||||
const STACK = 'stack';
|
const STACK = 'stack';
|
||||||
|
|
||||||
@ -46,24 +39,19 @@ function areEqual(prevProps, nextProps) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) {
|
||||||
prevProps.viewportOptions.orientation !==
|
|
||||||
nextProps.viewportOptions.orientation
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) {
|
||||||
prevProps.viewportOptions.toolGroupId !==
|
|
||||||
nextProps.viewportOptions.toolGroupId
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) {
|
||||||
prevProps.viewportOptions.viewportType !==
|
return false;
|
||||||
nextProps.viewportOptions.viewportType
|
}
|
||||||
) {
|
|
||||||
|
if (nextProps.viewportOptions.needsRerendering) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +67,7 @@ function areEqual(prevProps, nextProps) {
|
|||||||
|
|
||||||
const foundDisplaySet = nextDisplaySets.find(
|
const foundDisplaySet = nextDisplaySets.find(
|
||||||
nextDisplaySet =>
|
nextDisplaySet =>
|
||||||
nextDisplaySet.displaySetInstanceUID ===
|
nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID
|
||||||
prevDisplaySet.displaySetInstanceUID
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!foundDisplaySet) {
|
if (!foundDisplaySet) {
|
||||||
@ -95,9 +82,7 @@ function areEqual(prevProps, nextProps) {
|
|||||||
// check if their imageIds are the same
|
// check if their imageIds are the same
|
||||||
if (foundDisplaySet.images?.length) {
|
if (foundDisplaySet.images?.length) {
|
||||||
for (let j = 0; j < foundDisplaySet.images.length; j++) {
|
for (let j = 0; j < foundDisplaySet.images.length; j++) {
|
||||||
if (
|
if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) {
|
||||||
foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,25 +96,24 @@ function areEqual(prevProps, nextProps) {
|
|||||||
// Then we don't need to worry about the re-renders if the props change.
|
// Then we don't need to worry about the re-renders if the props change.
|
||||||
const OHIFCornerstoneViewport = React.memo(props => {
|
const OHIFCornerstoneViewport = React.memo(props => {
|
||||||
const {
|
const {
|
||||||
viewportIndex,
|
|
||||||
displaySets,
|
displaySets,
|
||||||
dataSource,
|
dataSource,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
displaySetOptions,
|
displaySetOptions,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
|
commandsManager,
|
||||||
onElementEnabled,
|
onElementEnabled,
|
||||||
onElementDisabled,
|
onElementDisabled,
|
||||||
|
isJumpToMeasurementDisabled,
|
||||||
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
||||||
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
||||||
// to set the initial state of the viewport's first image to render
|
// to set the initial state of the viewport's first image to render
|
||||||
initialImageIndex,
|
initialImageIndex,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const viewportId = viewportOptions.viewportId;
|
||||||
const [scrollbarHeight, setScrollbarHeight] = useState('100px');
|
const [scrollbarHeight, setScrollbarHeight] = useState('100px');
|
||||||
const [{ isCineEnabled, cines }, cineService] = useCine();
|
|
||||||
const [{ activeViewportIndex }] = useViewportGrid();
|
|
||||||
const [enabledVPElement, setEnabledVPElement] = useState(null);
|
const [enabledVPElement, setEnabledVPElement] = useState(null);
|
||||||
|
|
||||||
const elementRef = useRef();
|
const elementRef = useRef();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -145,74 +129,6 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
} = servicesManager.services as CornerstoneServices;
|
} = servicesManager.services as CornerstoneServices;
|
||||||
|
|
||||||
const [viewportDialogState] = useViewportDialog();
|
const [viewportDialogState] = useViewportDialog();
|
||||||
|
|
||||||
const cineHandler = () => {
|
|
||||||
if (!cines || !cines[viewportIndex] || !enabledVPElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cine = cines[viewportIndex];
|
|
||||||
const isPlaying = cine.isPlaying || false;
|
|
||||||
const frameRate = cine.frameRate || 24;
|
|
||||||
|
|
||||||
const validFrameRate = Math.max(frameRate, 1);
|
|
||||||
|
|
||||||
if (isPlaying) {
|
|
||||||
cineService.playClip(enabledVPElement, {
|
|
||||||
framesPerSecond: validFrameRate,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cineService.stopClip(enabledVPElement);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eventTarget.addEventListener(
|
|
||||||
Enums.Events.STACK_VIEWPORT_NEW_STACK,
|
|
||||||
cineHandler
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cineService.setCine({ id: viewportIndex, isPlaying: false });
|
|
||||||
eventTarget.removeEventListener(
|
|
||||||
Enums.Events.STACK_VIEWPORT_NEW_STACK,
|
|
||||||
cineHandler
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}, [enabledVPElement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!cines || !cines[viewportIndex] || !enabledVPElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cineHandler();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) {
|
|
||||||
cineService.stopClip(enabledVPElement);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]);
|
|
||||||
|
|
||||||
const cine = cines[viewportIndex];
|
|
||||||
const isPlaying = (cine && cine.isPlaying) || false;
|
|
||||||
|
|
||||||
const handleCineClose = () => {
|
|
||||||
toolbarService.recordInteraction({
|
|
||||||
groupId: 'MoreTools',
|
|
||||||
itemId: 'cine',
|
|
||||||
interactionType: 'toggle',
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
commandName: 'toggleCine',
|
|
||||||
commandOptions: {},
|
|
||||||
context: 'CORNERSTONE',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// useCallback for scroll bar height calculation
|
// useCallback for scroll bar height calculation
|
||||||
const setImageScrollBarHeight = useCallback(() => {
|
const setImageScrollBarHeight = useCallback(() => {
|
||||||
const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`;
|
const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`;
|
||||||
@ -227,54 +143,17 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
}
|
}
|
||||||
}, [elementRef]);
|
}, [elementRef]);
|
||||||
|
|
||||||
const storePresentation = () => {
|
const cleanUpServices = useCallback(
|
||||||
const currentPresentation = cornerstoneViewportService.getPresentation(
|
viewportInfo => {
|
||||||
viewportIndex
|
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
||||||
);
|
const syncGroups = viewportInfo.getSyncGroups();
|
||||||
if (!currentPresentation || !currentPresentation.presentationIds) return;
|
|
||||||
const {
|
|
||||||
lutPresentationStore,
|
|
||||||
positionPresentationStore,
|
|
||||||
} = stateSyncService.getState();
|
|
||||||
const { presentationIds } = currentPresentation;
|
|
||||||
const { lutPresentationId, positionPresentationId } = presentationIds || {};
|
|
||||||
const storeState = {};
|
|
||||||
if (lutPresentationId) {
|
|
||||||
storeState.lutPresentationStore = {
|
|
||||||
...lutPresentationStore,
|
|
||||||
[lutPresentationId]: currentPresentation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (positionPresentationId) {
|
|
||||||
storeState.positionPresentationStore = {
|
|
||||||
...positionPresentationStore,
|
|
||||||
[positionPresentationId]: currentPresentation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
stateSyncService.store(storeState);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanUpServices = useCallback(() => {
|
toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!viewportInfo) {
|
syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups);
|
||||||
return;
|
},
|
||||||
}
|
[viewportId]
|
||||||
|
);
|
||||||
const viewportId = viewportInfo.getViewportId();
|
|
||||||
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
|
||||||
const syncGroups = viewportInfo.getSyncGroups();
|
|
||||||
|
|
||||||
toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
|
|
||||||
|
|
||||||
syncGroupService.removeViewportFromSyncGroup(
|
|
||||||
viewportId,
|
|
||||||
renderingEngineId,
|
|
||||||
syncGroups
|
|
||||||
);
|
|
||||||
}, [viewportIndex, viewportOptions.viewportId]);
|
|
||||||
|
|
||||||
const elementEnabledHandler = useCallback(
|
const elementEnabledHandler = useCallback(
|
||||||
evt => {
|
evt => {
|
||||||
@ -284,71 +163,48 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { viewportId, element } = evt.detail;
|
const { viewportId, element } = evt.detail;
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(
|
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
viewportId
|
setEnabledElement(viewportId, element);
|
||||||
);
|
|
||||||
const viewportIndex = viewportInfo.getViewportIndex();
|
|
||||||
|
|
||||||
setEnabledElement(viewportIndex, element);
|
|
||||||
setEnabledVPElement(element);
|
setEnabledVPElement(element);
|
||||||
|
|
||||||
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
||||||
const toolGroupId = viewportInfo.getToolGroupId();
|
const toolGroupId = viewportInfo.getToolGroupId();
|
||||||
const syncGroups = viewportInfo.getSyncGroups();
|
const syncGroups = viewportInfo.getSyncGroups();
|
||||||
|
|
||||||
toolGroupService.addViewportToToolGroup(
|
toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId);
|
||||||
viewportId,
|
|
||||||
renderingEngineId,
|
|
||||||
toolGroupId
|
|
||||||
);
|
|
||||||
|
|
||||||
syncGroupService.addViewportToSyncGroup(
|
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
|
||||||
viewportId,
|
|
||||||
renderingEngineId,
|
|
||||||
syncGroups
|
|
||||||
);
|
|
||||||
|
|
||||||
if (onElementEnabled) {
|
if (onElementEnabled) {
|
||||||
onElementEnabled(evt);
|
onElementEnabled(evt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[viewportIndex, onElementEnabled, toolGroupService]
|
[viewportId, onElementEnabled, toolGroupService]
|
||||||
);
|
);
|
||||||
|
|
||||||
// disable the element upon unmounting
|
// disable the element upon unmounting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cornerstoneViewportService.enableViewport(
|
cornerstoneViewportService.enableViewport(viewportId, elementRef.current);
|
||||||
viewportIndex,
|
|
||||||
viewportOptions,
|
|
||||||
elementRef.current
|
|
||||||
);
|
|
||||||
|
|
||||||
eventTarget.addEventListener(
|
eventTarget.addEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
|
||||||
Enums.Events.ELEMENT_ENABLED,
|
|
||||||
elementEnabledHandler
|
|
||||||
);
|
|
||||||
|
|
||||||
setImageScrollBarHeight();
|
setImageScrollBarHeight();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
storePresentation();
|
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
|
|
||||||
cleanUpServices();
|
if (!viewportInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
cleanUpServices(viewportInfo);
|
||||||
viewportIndex
|
cornerstoneViewportService.storePresentation({ viewportId });
|
||||||
);
|
|
||||||
|
|
||||||
cornerstoneViewportService.disableElement(viewportIndex);
|
|
||||||
|
|
||||||
if (onElementDisabled) {
|
if (onElementDisabled) {
|
||||||
onElementDisabled(viewportInfo);
|
onElementDisabled(viewportInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventTarget.removeEventListener(
|
eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
|
||||||
Enums.Events.ELEMENT_ENABLED,
|
|
||||||
elementEnabledHandler
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -363,10 +219,15 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = displaySetService.subscribe(
|
const { unsubscribe } = displaySetService.subscribe(
|
||||||
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED,
|
||||||
async invalidatedDisplaySetInstanceUID => {
|
async ({
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
displaySetInstanceUID: invalidatedDisplaySetInstanceUID,
|
||||||
viewportIndex
|
invalidateData,
|
||||||
);
|
}: Types.DisplaySetSeriesMetadataInvalidatedEvent) => {
|
||||||
|
if (!invalidateData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
|
|
||||||
if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
|
if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
|
||||||
const viewportData = viewportInfo.getViewportData();
|
const viewportData = viewportInfo.getViewportData();
|
||||||
@ -378,18 +239,14 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const keepCamera = true;
|
const keepCamera = true;
|
||||||
cornerstoneViewportService.updateViewport(
|
cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera);
|
||||||
viewportIndex,
|
|
||||||
newViewportData,
|
|
||||||
keepCamera
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [viewportIndex]);
|
}, [viewportId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// handle the default viewportType to be stack
|
// handle the default viewportType to be stack
|
||||||
@ -408,33 +265,37 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
// The presentation state will have been stored previously by closing
|
// The presentation state will have been stored previously by closing
|
||||||
// a viewport. Otherwise, this viewport will be unchanged and the
|
// a viewport. Otherwise, this viewport will be unchanged and the
|
||||||
// presentation information will be directly carried over.
|
// presentation information will be directly carried over.
|
||||||
const {
|
const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState();
|
||||||
lutPresentationStore,
|
|
||||||
positionPresentationStore,
|
|
||||||
} = stateSyncService.getState();
|
|
||||||
const { presentationIds } = viewportOptions;
|
const { presentationIds } = viewportOptions;
|
||||||
const presentations = {
|
const presentations = {
|
||||||
positionPresentation:
|
positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId],
|
||||||
positionPresentationStore[presentationIds?.positionPresentationId],
|
lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId],
|
||||||
lutPresentation:
|
|
||||||
lutPresentationStore[presentationIds?.lutPresentationId],
|
|
||||||
};
|
};
|
||||||
let measurement;
|
let measurement;
|
||||||
if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) {
|
if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
|
||||||
measurement = cacheJumpToMeasurementEvent.measurement;
|
measurement = cacheJumpToMeasurementEvent.measurement;
|
||||||
// Delete the position presentation so that viewport navigates direct
|
// Delete the position presentation so that viewport navigates direct
|
||||||
presentations.positionPresentation = null;
|
presentations.positionPresentation = null;
|
||||||
cacheJumpToMeasurementEvent = null;
|
cacheJumpToMeasurementEvent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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. Not that we reached here we can turn it off.
|
||||||
|
if (viewportOptions.needsRerendering) {
|
||||||
|
viewportOptions.needsRerendering = false;
|
||||||
|
}
|
||||||
|
|
||||||
cornerstoneViewportService.setViewportData(
|
cornerstoneViewportService.setViewportData(
|
||||||
viewportIndex,
|
viewportId,
|
||||||
viewportData,
|
viewportData,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
displaySetOptions,
|
displaySetOptions,
|
||||||
presentations
|
presentations
|
||||||
);
|
);
|
||||||
|
|
||||||
if (measurement) {
|
if (measurement) {
|
||||||
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
|
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
|
||||||
}
|
}
|
||||||
@ -454,11 +315,15 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
* the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
|
* the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isJumpToMeasurementDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
|
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
@ -468,7 +333,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
@ -477,17 +342,14 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
return () => {
|
return () => {
|
||||||
unsubscribeFromJumpToMeasurementEvents();
|
unsubscribeFromJumpToMeasurementEvents();
|
||||||
};
|
};
|
||||||
}, [displaySets, elementRef, viewportIndex]);
|
}, [displaySets, elementRef, viewportId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className="viewport-wrapper">
|
<div className="viewport-wrapper">
|
||||||
<ReactResizeDetector
|
<ReactResizeDetector
|
||||||
handleWidth
|
refreshMode="debounce"
|
||||||
handleHeight
|
refreshRate={50} // Wait 50 ms after last move to render
|
||||||
skipOnMount={true} // Todo: make these configurable
|
|
||||||
refreshMode={'debounce'}
|
|
||||||
refreshRate={200} // transition amount in side panel
|
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
targetRef={elementRef.current}
|
targetRef={elementRef.current}
|
||||||
/>
|
/>
|
||||||
@ -499,34 +361,20 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
ref={elementRef}
|
ref={elementRef}
|
||||||
></div>
|
></div>
|
||||||
<CornerstoneOverlays
|
<CornerstoneOverlays
|
||||||
viewportIndex={viewportIndex}
|
viewportId={viewportId}
|
||||||
toolBarService={toolbarService}
|
toolBarService={toolbarService}
|
||||||
element={elementRef.current}
|
element={elementRef.current}
|
||||||
scrollbarHeight={scrollbarHeight}
|
scrollbarHeight={scrollbarHeight}
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
/>
|
/>
|
||||||
{isCineEnabled && (
|
<CinePlayer
|
||||||
<CinePlayer
|
enabledVPElement={enabledVPElement}
|
||||||
className="absolute left-1/2 -translate-x-1/2 bottom-3"
|
viewportId={viewportId}
|
||||||
isPlaying={isPlaying}
|
servicesManager={servicesManager}
|
||||||
onClose={handleCineClose}
|
/>
|
||||||
onPlayPauseChange={isPlaying =>
|
|
||||||
cineService.setCine({
|
|
||||||
id: activeViewportIndex,
|
|
||||||
isPlaying,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onFrameRateChange={frameRate =>
|
|
||||||
cineService.setCine({
|
|
||||||
id: activeViewportIndex,
|
|
||||||
frameRate,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute w-full">
|
<div className="absolute w-full">
|
||||||
{viewportDialogState.viewportIndex === viewportIndex && (
|
{viewportDialogState.viewportId === viewportId && (
|
||||||
<Notification
|
<Notification
|
||||||
id="viewport-notification"
|
id="viewport-notification"
|
||||||
message={viewportDialogState.message}
|
message={viewportDialogState.message}
|
||||||
@ -545,35 +393,35 @@ function _subscribeToJumpToMeasurementEvents(
|
|||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
) {
|
) {
|
||||||
const displaysUIDs = displaySets.map(
|
|
||||||
displaySet => displaySet.displaySetInstanceUID
|
|
||||||
);
|
|
||||||
const { unsubscribe } = measurementService.subscribe(
|
const { unsubscribe } = measurementService.subscribe(
|
||||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
|
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
|
||||||
props => {
|
props => {
|
||||||
cacheJumpToMeasurementEvent = props;
|
cacheJumpToMeasurementEvent = props;
|
||||||
const { viewportIndex: jumpIndex, measurement, isConsumed } = props;
|
const { viewportId: jumpId, measurement, isConsumed } = props;
|
||||||
if (!measurement || isConsumed) return;
|
if (!measurement || isConsumed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
|
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
|
||||||
// Decide on which viewport should handle this
|
// Decide on which viewport should handle this
|
||||||
cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump(
|
cacheJumpToMeasurementEvent.cornerstoneViewport =
|
||||||
jumpIndex,
|
cornerstoneViewportService.getViewportIdToJump(
|
||||||
measurement.displaySetInstanceUID,
|
jumpId,
|
||||||
{ referencedImageId: measurement.referencedImageId }
|
measurement.displaySetInstanceUID,
|
||||||
);
|
{ referencedImageId: measurement.referencedImageId }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) {
|
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_jumpToMeasurement(
|
_jumpToMeasurement(
|
||||||
measurement,
|
measurement,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
@ -590,20 +438,22 @@ function _checkForCachedJumpToMeasurementEvents(
|
|||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
displaySets,
|
displaySets,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
) {
|
) {
|
||||||
if (!cacheJumpToMeasurementEvent) return;
|
if (!cacheJumpToMeasurementEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cacheJumpToMeasurementEvent.isConsumed) {
|
if (cacheJumpToMeasurementEvent.isConsumed) {
|
||||||
cacheJumpToMeasurementEvent = null;
|
cacheJumpToMeasurementEvent = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const displaysUIDs = displaySets.map(
|
const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
|
||||||
displaySet => displaySet.displaySetInstanceUID
|
if (!displaysUIDs?.length) {
|
||||||
);
|
return;
|
||||||
if (!displaysUIDs?.length) return;
|
}
|
||||||
|
|
||||||
// Jump to measurement if the measurement exists
|
// Jump to measurement if the measurement exists
|
||||||
const { measurement } = cacheJumpToMeasurementEvent;
|
const { measurement } = cacheJumpToMeasurementEvent;
|
||||||
@ -612,7 +462,7 @@ function _checkForCachedJumpToMeasurementEvents(
|
|||||||
_jumpToMeasurement(
|
_jumpToMeasurement(
|
||||||
measurement,
|
measurement,
|
||||||
elementRef,
|
elementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
@ -625,7 +475,7 @@ function _checkForCachedJumpToMeasurementEvents(
|
|||||||
function _jumpToMeasurement(
|
function _jumpToMeasurement(
|
||||||
measurement,
|
measurement,
|
||||||
targetElementRef,
|
targetElementRef,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
measurementService,
|
measurementService,
|
||||||
displaySetService,
|
displaySetService,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
@ -639,27 +489,19 @@ function _jumpToMeasurement(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
displaySetInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
|
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
|
||||||
// to set it properly
|
// to set it properly
|
||||||
// setCornerstoneMeasurementActive(measurement);
|
// setCornerstoneMeasurementActive(measurement);
|
||||||
|
|
||||||
viewportGridService.setActiveViewportIndex(viewportIndex);
|
viewportGridService.setActiveViewportId(viewportId);
|
||||||
|
|
||||||
const enabledElement = getEnabledElement(targetElement);
|
const enabledElement = getEnabledElement(targetElement);
|
||||||
|
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (enabledElement) {
|
if (enabledElement) {
|
||||||
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
|
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
|
||||||
const viewport = enabledElement.viewport as
|
const viewport = enabledElement.viewport as IStackViewport | IVolumeViewport;
|
||||||
| IStackViewport
|
|
||||||
| IVolumeViewport;
|
|
||||||
|
|
||||||
let imageIdIndex = 0;
|
let imageIdIndex = 0;
|
||||||
let viewportCameraDirectionMatch = true;
|
let viewportCameraDirectionMatch = true;
|
||||||
@ -667,14 +509,9 @@ function _jumpToMeasurement(
|
|||||||
if (viewport instanceof StackViewport) {
|
if (viewport instanceof StackViewport) {
|
||||||
const imageIds = viewport.getImageIds();
|
const imageIds = viewport.getImageIds();
|
||||||
imageIdIndex = imageIds.findIndex(imageId => {
|
imageIdIndex = imageIds.findIndex(imageId => {
|
||||||
const {
|
const { SOPInstanceUID: aSOPInstanceUID, frameNumber: aFrameNumber } =
|
||||||
SOPInstanceUID: aSOPInstanceUID,
|
getSOPInstanceAttributes(imageId);
|
||||||
frameNumber: aFrameNumber,
|
return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber);
|
||||||
} = getSOPInstanceAttributes(imageId);
|
|
||||||
return (
|
|
||||||
aSOPInstanceUID === SOPInstanceUID &&
|
|
||||||
(!frameNumber || frameNumber === aFrameNumber)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// for volume viewport we can't rely on the imageIdIndex since it can be
|
// for volume viewport we can't rely on the imageIdIndex since it can be
|
||||||
@ -689,10 +526,7 @@ function _jumpToMeasurement(
|
|||||||
// should compare abs for both planes since the direction can be flipped
|
// should compare abs for both planes since the direction can be flipped
|
||||||
if (
|
if (
|
||||||
measurementViewPlane &&
|
measurementViewPlane &&
|
||||||
!csUtils.isEqual(
|
!csUtils.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs))
|
||||||
measurementViewPlane.map(Math.abs),
|
|
||||||
viewportViewPlane.map(Math.abs)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
viewportCameraDirectionMatch = false;
|
viewportCameraDirectionMatch = false;
|
||||||
}
|
}
|
||||||
@ -716,21 +550,22 @@ function _jumpToMeasurement(
|
|||||||
// Component displayName
|
// Component displayName
|
||||||
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
|
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
|
||||||
|
|
||||||
|
OHIFCornerstoneViewport.defaultProps = {
|
||||||
|
isJumpToMeasurementDisabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
OHIFCornerstoneViewport.propTypes = {
|
OHIFCornerstoneViewport.propTypes = {
|
||||||
viewportIndex: PropTypes.number.isRequired,
|
|
||||||
displaySets: PropTypes.array.isRequired,
|
displaySets: PropTypes.array.isRequired,
|
||||||
dataSource: PropTypes.object.isRequired,
|
dataSource: PropTypes.object.isRequired,
|
||||||
viewportOptions: PropTypes.object,
|
viewportOptions: PropTypes.object,
|
||||||
displaySetOptions: PropTypes.arrayOf(PropTypes.any),
|
displaySetOptions: PropTypes.arrayOf(PropTypes.any),
|
||||||
servicesManager: PropTypes.object.isRequired,
|
servicesManager: PropTypes.object.isRequired,
|
||||||
onElementEnabled: PropTypes.func,
|
onElementEnabled: PropTypes.func,
|
||||||
|
isJumpToMeasurementDisabled: PropTypes.bool,
|
||||||
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
||||||
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
||||||
// to set the initial state of the viewport's first image to render
|
// to set the initial state of the viewport's first image to render
|
||||||
initialImageIdOrIndex: PropTypes.oneOfType([
|
initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OHIFCornerstoneViewport;
|
export default OHIFCornerstoneViewport;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers';
|
|||||||
import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator';
|
import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator';
|
||||||
|
|
||||||
function CornerstoneOverlays(props) {
|
function CornerstoneOverlays(props) {
|
||||||
const { viewportIndex, element, scrollbarHeight, servicesManager } = props;
|
const { viewportId, element, scrollbarHeight, servicesManager } = props;
|
||||||
const { cornerstoneViewportService } = servicesManager.services;
|
const { cornerstoneViewportService } = servicesManager.services;
|
||||||
const [imageSliceData, setImageSliceData] = useState({
|
const [imageSliceData, setImageSliceData] = useState({
|
||||||
imageIndex: 0,
|
imageIndex: 0,
|
||||||
@ -18,7 +18,7 @@ function CornerstoneOverlays(props) {
|
|||||||
const { unsubscribe } = cornerstoneViewportService.subscribe(
|
const { unsubscribe } = cornerstoneViewportService.subscribe(
|
||||||
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
|
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
|
||||||
props => {
|
props => {
|
||||||
if (props.viewportIndex !== viewportIndex) {
|
if (props.viewportId !== viewportId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,15 +29,14 @@ function CornerstoneOverlays(props) {
|
|||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [viewportIndex]);
|
}, [viewportId]);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportData) {
|
if (viewportData) {
|
||||||
const viewportInfo =
|
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
||||||
|
|
||||||
if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
|
if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
|
||||||
return null;
|
return null;
|
||||||
@ -47,7 +46,7 @@ function CornerstoneOverlays(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="noselect">
|
<div className="noselect">
|
||||||
<ViewportImageScrollbar
|
<ViewportImageScrollbar
|
||||||
viewportIndex={viewportIndex}
|
viewportId={viewportId}
|
||||||
viewportData={viewportData}
|
viewportData={viewportData}
|
||||||
element={element}
|
element={element}
|
||||||
imageSliceData={imageSliceData}
|
imageSliceData={imageSliceData}
|
||||||
@ -59,7 +58,7 @@ function CornerstoneOverlays(props) {
|
|||||||
<CustomizableViewportOverlay
|
<CustomizableViewportOverlay
|
||||||
imageSliceData={imageSliceData}
|
imageSliceData={imageSliceData}
|
||||||
viewportData={viewportData}
|
viewportData={viewportData}
|
||||||
viewportIndex={viewportIndex}
|
viewportId={viewportId}
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
element={element}
|
element={element}
|
||||||
/>
|
/>
|
||||||
@ -74,7 +73,7 @@ function CornerstoneOverlays(props) {
|
|||||||
element={element}
|
element={element}
|
||||||
viewportData={viewportData}
|
viewportData={viewportData}
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
viewportIndex={viewportIndex}
|
viewportId={viewportId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,12 +3,7 @@ import { vec3 } from 'gl-matrix';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
||||||
import { ViewportOverlay } from '@ohif/ui';
|
import { ViewportOverlay } from '@ohif/ui';
|
||||||
import {
|
import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||||
formatPN,
|
|
||||||
formatDICOMDate,
|
|
||||||
formatDICOMTime,
|
|
||||||
formatNumberPrecision,
|
|
||||||
} from './utils';
|
|
||||||
import { InstanceMetadata } from 'platform/core/src/types';
|
import { InstanceMetadata } from 'platform/core/src/types';
|
||||||
import { ServicesManager } from '@ohif/core';
|
import { ServicesManager } from '@ohif/core';
|
||||||
import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types';
|
import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types';
|
||||||
@ -21,7 +16,6 @@ interface OverlayItemProps {
|
|||||||
element: any;
|
element: any;
|
||||||
viewportData: any;
|
viewportData: any;
|
||||||
imageSliceData: ImageSliceData;
|
imageSliceData: ImageSliceData;
|
||||||
viewportIndex: number | null;
|
|
||||||
servicesManager: ServicesManager;
|
servicesManager: ServicesManager;
|
||||||
instance: InstanceMetadata;
|
instance: InstanceMetadata;
|
||||||
customization: any;
|
customization: any;
|
||||||
@ -56,13 +50,9 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
|||||||
style={{ color: (customization && customization.color) || undefined }}
|
style={{ color: (customization && customization.color) || undefined }}
|
||||||
>
|
>
|
||||||
<span className="mr-1 shrink-0">W:</span>
|
<span className="mr-1 shrink-0">W:</span>
|
||||||
<span className="ml-1 mr-2 font-light shrink-0">
|
<span className="ml-1 mr-2 shrink-0 font-light">{windowWidth.toFixed(0)}</span>
|
||||||
{windowWidth.toFixed(0)}
|
|
||||||
</span>
|
|
||||||
<span className="mr-1 shrink-0">L:</span>
|
<span className="mr-1 shrink-0">L:</span>
|
||||||
<span className="ml-1 font-light shrink-0">
|
<span className="ml-1 shrink-0 font-light">{windowCenter.toFixed(0)}</span>
|
||||||
{windowCenter.toFixed(0)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -114,14 +104,11 @@ function CustomizableViewportOverlay({
|
|||||||
element,
|
element,
|
||||||
viewportData,
|
viewportData,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { toolbarService, cornerstoneViewportService, customizationService } =
|
||||||
toolbarService,
|
servicesManager.services;
|
||||||
cornerstoneViewportService,
|
|
||||||
customizationService,
|
|
||||||
} = servicesManager.services;
|
|
||||||
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
const [activeTools, setActiveTools] = useState([]);
|
const [activeTools, setActiveTools] = useState([]);
|
||||||
@ -150,15 +137,10 @@ function CustomizableViewportOverlay({
|
|||||||
|
|
||||||
const instanceNumber = useMemo(() => {
|
const instanceNumber = useMemo(() => {
|
||||||
if (viewportData != null) {
|
if (viewportData != null) {
|
||||||
return _getInstanceNumber(
|
return _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService);
|
||||||
viewportData,
|
|
||||||
viewportIndex,
|
|
||||||
imageIndex,
|
|
||||||
cornerstoneViewportService
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]);
|
}, [viewportData, viewportId, imageIndex, cornerstoneViewportService]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial toolbar state
|
* Initial toolbar state
|
||||||
@ -179,10 +161,7 @@ function CustomizableViewportOverlay({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { lower, upper } = range;
|
const { lower, upper } = range;
|
||||||
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
|
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper);
|
||||||
lower,
|
|
||||||
upper
|
|
||||||
);
|
|
||||||
|
|
||||||
setVOI({ windowCenter, windowWidth });
|
setVOI({ windowCenter, windowWidth });
|
||||||
};
|
};
|
||||||
@ -192,7 +171,7 @@ function CustomizableViewportOverlay({
|
|||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||||
};
|
};
|
||||||
}, [viewportIndex, viewportData, voi, element]);
|
}, [viewportId, viewportData, voi, element]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updating the scale when the viewport changes its zoom
|
* Updating the scale when the viewport changes its zoom
|
||||||
@ -205,9 +184,7 @@ function CustomizableViewportOverlay({
|
|||||||
previousCamera.parallelScale !== camera.parallelScale ||
|
previousCamera.parallelScale !== camera.parallelScale ||
|
||||||
previousCamera.scale !== camera.scale
|
previousCamera.scale !== camera.scale
|
||||||
) {
|
) {
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!viewport) {
|
if (!viewport) {
|
||||||
return;
|
return;
|
||||||
@ -226,8 +203,7 @@ function CustomizableViewportOverlay({
|
|||||||
|
|
||||||
const { spacing } = imageData;
|
const { spacing } = imageData;
|
||||||
// convert parallel scale to scale
|
// convert parallel scale to scale
|
||||||
const scale =
|
const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale;
|
||||||
(element.clientHeight * spacing[0] * 0.5) / camera.parallelScale;
|
|
||||||
setScale(scale);
|
setScale(scale);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -237,7 +213,7 @@ function CustomizableViewportOverlay({
|
|||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
||||||
};
|
};
|
||||||
}, [viewportIndex, viewportData, cornerstoneViewportService, element]);
|
}, [viewportId, viewportData, cornerstoneViewportService, element]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updating the active tools when the toolbar changes
|
* Updating the active tools when the toolbar changes
|
||||||
@ -262,7 +238,7 @@ function CustomizableViewportOverlay({
|
|||||||
element,
|
element,
|
||||||
viewportData,
|
viewportData,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
customization: item,
|
customization: item,
|
||||||
formatters: {
|
formatters: {
|
||||||
@ -296,7 +272,7 @@ function CustomizableViewportOverlay({
|
|||||||
element,
|
element,
|
||||||
viewportData,
|
viewportData,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
customizationService,
|
customizationService,
|
||||||
instance,
|
instance,
|
||||||
@ -343,9 +319,7 @@ function CustomizableViewportOverlay({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<div key={`bottomLeftOverlayItem_${i}`}>
|
<div key={`bottomLeftOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||||
{_renderOverlayItem(item)}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -356,9 +330,7 @@ function CustomizableViewportOverlay({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<div key={`bottomRightOverlayItem_${i}`}>
|
<div key={`bottomRightOverlayItem_${i}`}>{_renderOverlayItem(item)}</div>
|
||||||
{_renderOverlayItem(item)}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -379,7 +351,7 @@ function _getViewportInstance(viewportData, imageIndex) {
|
|||||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||||
imageId = viewportData.data.imageIds[imageIndex];
|
imageId = viewportData.data.imageIds[imageIndex];
|
||||||
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||||
const volumes = viewportData.volumes;
|
const volumes = viewportData.data;
|
||||||
if (volumes && volumes.length == 1) {
|
if (volumes && volumes.length == 1) {
|
||||||
const volume = volumes[0];
|
const volume = volumes[0];
|
||||||
imageId = volume.imageIds[imageIndex];
|
imageId = volume.imageIds[imageIndex];
|
||||||
@ -388,12 +360,7 @@ function _getViewportInstance(viewportData, imageIndex) {
|
|||||||
return imageId ? metaData.get('instance', imageId) || {} : {};
|
return imageId ? metaData.get('instance', imageId) || {} : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getInstanceNumber(
|
function _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) {
|
||||||
viewportData,
|
|
||||||
viewportIndex,
|
|
||||||
imageIndex,
|
|
||||||
cornerstoneViewportService
|
|
||||||
) {
|
|
||||||
let instanceNumber;
|
let instanceNumber;
|
||||||
|
|
||||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||||
@ -406,7 +373,7 @@ function _getInstanceNumber(
|
|||||||
instanceNumber = _getInstanceNumberFromVolume(
|
instanceNumber = _getInstanceNumberFromVolume(
|
||||||
viewportData,
|
viewportData,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -436,12 +403,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
|||||||
// Since volume viewports can be in any view direction, they can render
|
// Since volume viewports can be in any view direction, they can render
|
||||||
// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
|
// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
|
||||||
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
|
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
|
||||||
function _getInstanceNumberFromVolume(
|
function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService) {
|
||||||
viewportData,
|
|
||||||
imageIndex,
|
|
||||||
viewportIndex,
|
|
||||||
cornerstoneViewportService
|
|
||||||
) {
|
|
||||||
const volumes = viewportData.volumes;
|
const volumes = viewportData.volumes;
|
||||||
|
|
||||||
// Todo: support fusion of acquisition plane which has instanceNumber
|
// Todo: support fusion of acquisition plane which has instanceNumber
|
||||||
@ -452,9 +414,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
const volume = volumes[0];
|
const volume = volumes[0];
|
||||||
const { direction, imageIds } = volume;
|
const { direction, imageIds } = volume;
|
||||||
|
|
||||||
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cornerstoneViewport) {
|
if (!cornerstoneViewport) {
|
||||||
return;
|
return;
|
||||||
@ -477,8 +437,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { instanceNumber } =
|
const { instanceNumber } = metaData.get('generalImageModule', imageId) || {};
|
||||||
metaData.get('generalImageModule', imageId) || {};
|
|
||||||
return parseInt(instanceNumber);
|
return parseInt(instanceNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,7 +445,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
CustomizableViewportOverlay.propTypes = {
|
CustomizableViewportOverlay.propTypes = {
|
||||||
viewportData: PropTypes.object,
|
viewportData: PropTypes.object,
|
||||||
imageIndex: PropTypes.number,
|
imageIndex: PropTypes.number,
|
||||||
viewportIndex: PropTypes.number,
|
viewportId: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomizableViewportOverlay;
|
export default CustomizableViewportOverlay;
|
||||||
|
|||||||
@ -7,34 +7,24 @@ import { ServicesManger } from '@ohif/core';
|
|||||||
|
|
||||||
function CornerstoneImageScrollbar({
|
function CornerstoneImageScrollbar({
|
||||||
viewportData,
|
viewportData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
element,
|
element,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
setImageSliceData,
|
setImageSliceData,
|
||||||
scrollbarHeight,
|
scrollbarHeight,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { cineService, cornerstoneViewportService } = (servicesManager as ServicesManger).services;
|
||||||
cineService,
|
|
||||||
cornerstoneViewportService,
|
|
||||||
} = (servicesManager as ServicesManger).services;
|
|
||||||
|
|
||||||
const onImageScrollbarChange = (imageIndex, viewportIndex) => {
|
const onImageScrollbarChange = (imageIndex, viewportId) => {
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewportId = viewportInfo.getViewportId();
|
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(
|
|
||||||
viewportId
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isCineEnabled } = cineService.getState();
|
const { isCineEnabled } = cineService.getState();
|
||||||
|
|
||||||
if (isCineEnabled) {
|
if (isCineEnabled) {
|
||||||
// on image scrollbar change, stop the CINE if it is playing
|
// on image scrollbar change, stop the CINE if it is playing
|
||||||
cineService.stopClip(element);
|
cineService.stopClip(element);
|
||||||
cineService.setCine({ id: viewportIndex, isPlaying: false });
|
cineService.setCine({ id: viewportId, isPlaying: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
csToolsUtils.jumpToSlice(viewport.element, {
|
csToolsUtils.jumpToSlice(viewport.element, {
|
||||||
@ -48,9 +38,7 @@ function CornerstoneImageScrollbar({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!viewport) {
|
if (!viewport) {
|
||||||
return;
|
return;
|
||||||
@ -79,7 +67,7 @@ function CornerstoneImageScrollbar({
|
|||||||
const { imageIndex, numberOfSlices } = sliceData;
|
const { imageIndex, numberOfSlices } = sliceData;
|
||||||
setImageSliceData({ imageIndex, numberOfSlices });
|
setImageSliceData({ imageIndex, numberOfSlices });
|
||||||
}
|
}
|
||||||
}, [viewportIndex, viewportData]);
|
}, [viewportId, viewportData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewportData?.viewportType !== Enums.ViewportType.STACK) {
|
if (viewportData?.viewportType !== Enums.ViewportType.STACK) {
|
||||||
@ -95,16 +83,10 @@ function CornerstoneImageScrollbar({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
element.addEventListener(
|
element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
||||||
Enums.Events.STACK_VIEWPORT_SCROLL,
|
|
||||||
updateStackIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(
|
element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
||||||
Enums.Events.STACK_VIEWPORT_SCROLL,
|
|
||||||
updateStackIndex
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, [viewportData, element]);
|
}, [viewportData, element]);
|
||||||
|
|
||||||
@ -122,19 +104,14 @@ function CornerstoneImageScrollbar({
|
|||||||
element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(
|
element.removeEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
||||||
Enums.Events.VOLUME_NEW_IMAGE,
|
|
||||||
updateVolumeIndex
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, [viewportData, element]);
|
}, [viewportData, element]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageScrollbar
|
<ImageScrollbar
|
||||||
onChange={evt => onImageScrollbarChange(evt, viewportIndex)}
|
onChange={evt => onImageScrollbarChange(evt, viewportId)}
|
||||||
max={
|
max={imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0}
|
||||||
imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0
|
|
||||||
}
|
|
||||||
height={scrollbarHeight}
|
height={scrollbarHeight}
|
||||||
value={imageSliceData.imageIndex}
|
value={imageSliceData.imageIndex}
|
||||||
/>
|
/>
|
||||||
@ -143,7 +120,7 @@ function CornerstoneImageScrollbar({
|
|||||||
|
|
||||||
CornerstoneImageScrollbar.propTypes = {
|
CornerstoneImageScrollbar.propTypes = {
|
||||||
viewportData: PropTypes.object,
|
viewportData: PropTypes.object,
|
||||||
viewportIndex: PropTypes.number.isRequired,
|
viewportId: PropTypes.string.isRequired,
|
||||||
element: PropTypes.instanceOf(Element),
|
element: PropTypes.instanceOf(Element),
|
||||||
scrollbarHeight: PropTypes.string,
|
scrollbarHeight: PropTypes.string,
|
||||||
imageSliceData: PropTypes.object.isRequired,
|
imageSliceData: PropTypes.object.isRequired,
|
||||||
|
|||||||
@ -33,26 +33,14 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
element.addEventListener(
|
element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
|
||||||
Enums.Events.STACK_VIEWPORT_SCROLL,
|
|
||||||
setLoadingState
|
|
||||||
);
|
|
||||||
element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
||||||
element.addEventListener(
|
element.addEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
|
||||||
Enums.Events.STACK_NEW_IMAGE,
|
|
||||||
setFinishLoadingState
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(
|
element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
|
||||||
Enums.Events.STACK_VIEWPORT_SCROLL,
|
|
||||||
setLoadingState
|
|
||||||
);
|
|
||||||
|
|
||||||
element.removeEventListener(
|
element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
|
||||||
Enums.Events.STACK_NEW_IMAGE,
|
|
||||||
setFinishLoadingState
|
|
||||||
);
|
|
||||||
|
|
||||||
element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
||||||
};
|
};
|
||||||
@ -61,8 +49,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-black opacity-50 absolute h-full w-full top-0 left-0">
|
<div className="absolute top-0 left-0 h-full w-full bg-black opacity-50">
|
||||||
<div className="flex transparent items-center justify-center w-full h-full">
|
<div className="transparent flex h-full w-full items-center justify-center">
|
||||||
<p className="text-primary-light text-xl font-light">
|
<p className="text-primary-light text-xl font-light">
|
||||||
<h4>Error Loading Image</h4>
|
<h4>Error Loading Image</h4>
|
||||||
<p>An error has occurred.</p>
|
<p>An error has occurred.</p>
|
||||||
@ -78,8 +66,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) {
|
|||||||
return (
|
return (
|
||||||
// IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from
|
// IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from
|
||||||
// interacting with the mouse, since scrolling should propagate to the viewport underneath
|
// interacting with the mouse, since scrolling should propagate to the viewport underneath
|
||||||
<div className="pointer-events-none bg-black opacity-50 absolute h-full w-full top-0 left-0">
|
<div className="pointer-events-none absolute top-0 left-0 h-full w-full bg-black opacity-50">
|
||||||
<div className="flex transparent items-center justify-center w-full h-full">
|
<div className="transparent flex h-full w-full items-center justify-center">
|
||||||
<p className="text-primary-light text-xl font-light">Loading...</p>
|
<p className="text-primary-light text-xl font-light">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,16 +13,13 @@ import { vec3 } from 'gl-matrix';
|
|||||||
|
|
||||||
import './ViewportOrientationMarkers.css';
|
import './ViewportOrientationMarkers.css';
|
||||||
|
|
||||||
const {
|
const { getOrientationStringLPS, invertOrientationStringLPS } = utilities.orientation;
|
||||||
getOrientationStringLPS,
|
|
||||||
invertOrientationStringLPS,
|
|
||||||
} = utilities.orientation;
|
|
||||||
|
|
||||||
function ViewportOrientationMarkers({
|
function ViewportOrientationMarkers({
|
||||||
element,
|
element,
|
||||||
viewportData,
|
viewportData,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
orientationMarkers = ['top', 'left'],
|
orientationMarkers = ['top', 'left'],
|
||||||
}) {
|
}) {
|
||||||
@ -33,9 +30,7 @@ function ViewportOrientationMarkers({
|
|||||||
const { cornerstoneViewportService } = servicesManager.services;
|
const { cornerstoneViewportService } = servicesManager.services;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cameraModifiedListener = (
|
const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => {
|
||||||
evt: Types.EventTypes.CameraModifiedEvent
|
|
||||||
) => {
|
|
||||||
const { rotation, previousCamera, camera } = evt.detail;
|
const { rotation, previousCamera, camera } = evt.detail;
|
||||||
|
|
||||||
if (rotation !== undefined) {
|
if (rotation !== undefined) {
|
||||||
@ -57,16 +52,10 @@ function ViewportOrientationMarkers({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
element.addEventListener(
|
element.addEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
|
||||||
Enums.Events.CAMERA_MODIFIED,
|
|
||||||
cameraModifiedListener
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(
|
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
|
||||||
Enums.Events.CAMERA_MODIFIED,
|
|
||||||
cameraModifiedListener
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -85,8 +74,7 @@ function ViewportOrientationMarkers({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
({ rowCosines, columnCosines } =
|
({ rowCosines, columnCosines } = metaData.get('imagePlaneModule', imageId) || {});
|
||||||
metaData.get('imagePlaneModule', imageId) || {});
|
|
||||||
} else {
|
} else {
|
||||||
if (!element || !getEnabledElement(element)) {
|
if (!element || !getEnabledElement(element)) {
|
||||||
return '';
|
return '';
|
||||||
@ -114,9 +102,7 @@ function ViewportOrientationMarkers({
|
|||||||
flipHorizontal
|
flipHorizontal
|
||||||
);
|
);
|
||||||
|
|
||||||
const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex(
|
const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ohifViewport) {
|
if (!ohifViewport) {
|
||||||
console.log('ViewportOrientationMarkers::No viewport');
|
console.log('ViewportOrientationMarkers::No viewport');
|
||||||
@ -126,9 +112,7 @@ function ViewportOrientationMarkers({
|
|||||||
|
|
||||||
// Todo: probably this can be done in a better way in which we identify bright
|
// Todo: probably this can be done in a better way in which we identify bright
|
||||||
// background
|
// background
|
||||||
const isLight = backgroundColor
|
const isLight = backgroundColor ? csUtils.isEqual(backgroundColor, [1, 1, 1]) : false;
|
||||||
? csUtils.isEqual(backgroundColor, [1, 1, 1])
|
|
||||||
: false;
|
|
||||||
|
|
||||||
return orientationMarkers.map((m, index) => (
|
return orientationMarkers.map((m, index) => (
|
||||||
<div
|
<div
|
||||||
@ -174,13 +158,7 @@ ViewportOrientationMarkers.defaultProps = {
|
|||||||
* @param {*} rotation in degrees
|
* @param {*} rotation in degrees
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function _getOrientationMarkers(
|
function _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal) {
|
||||||
rowCosines,
|
|
||||||
columnCosines,
|
|
||||||
rotation,
|
|
||||||
flipVertical,
|
|
||||||
flipHorizontal
|
|
||||||
) {
|
|
||||||
const rowString = getOrientationStringLPS(rowCosines);
|
const rowString = getOrientationStringLPS(rowCosines);
|
||||||
const columnString = getOrientationStringLPS(columnCosines);
|
const columnString = getOrientationStringLPS(columnCosines);
|
||||||
const oppositeRowString = invertOrientationStringLPS(rowString);
|
const oppositeRowString = invertOrientationStringLPS(rowString);
|
||||||
|
|||||||
@ -11,13 +11,10 @@ function CornerstoneViewportOverlay({
|
|||||||
element,
|
element,
|
||||||
viewportData,
|
viewportData,
|
||||||
imageSliceData,
|
imageSliceData,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { cornerstoneViewportService, toolbarService } = servicesManager.services;
|
||||||
cornerstoneViewportService,
|
|
||||||
toolbarService,
|
|
||||||
} = servicesManager.services;
|
|
||||||
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
||||||
const [scale, setScale] = useState(1);
|
const [scale, setScale] = useState(1);
|
||||||
const [activeTools, setActiveTools] = useState([]);
|
const [activeTools, setActiveTools] = useState([]);
|
||||||
@ -60,10 +57,7 @@ function CornerstoneViewportOverlay({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { lower, upper } = range;
|
const { lower, upper } = range;
|
||||||
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
|
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper);
|
||||||
lower,
|
|
||||||
upper
|
|
||||||
);
|
|
||||||
|
|
||||||
setVOI({ windowCenter, windowWidth });
|
setVOI({ windowCenter, windowWidth });
|
||||||
};
|
};
|
||||||
@ -73,7 +67,7 @@ function CornerstoneViewportOverlay({
|
|||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||||
};
|
};
|
||||||
}, [viewportIndex, viewportData, voi, element]);
|
}, [viewportId, viewportData, voi, element]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updating the scale when the viewport changes its zoom
|
* Updating the scale when the viewport changes its zoom
|
||||||
@ -86,9 +80,7 @@ function CornerstoneViewportOverlay({
|
|||||||
previousCamera.parallelScale !== camera.parallelScale ||
|
previousCamera.parallelScale !== camera.parallelScale ||
|
||||||
previousCamera.scale !== camera.scale
|
previousCamera.scale !== camera.scale
|
||||||
) {
|
) {
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!viewport) {
|
if (!viewport) {
|
||||||
return;
|
return;
|
||||||
@ -107,8 +99,7 @@ function CornerstoneViewportOverlay({
|
|||||||
|
|
||||||
const { spacing } = imageData;
|
const { spacing } = imageData;
|
||||||
// convert parallel scale to scale
|
// convert parallel scale to scale
|
||||||
const scale =
|
const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale;
|
||||||
(element.clientHeight * spacing[0] * 0.5) / camera.parallelScale;
|
|
||||||
setScale(scale);
|
setScale(scale);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -118,7 +109,7 @@ function CornerstoneViewportOverlay({
|
|||||||
return () => {
|
return () => {
|
||||||
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
|
||||||
};
|
};
|
||||||
}, [viewportIndex, viewportData]);
|
}, [viewportId, viewportData]);
|
||||||
|
|
||||||
const getTopLeftContent = useCallback(() => {
|
const getTopLeftContent = useCallback(() => {
|
||||||
const { windowWidth, windowCenter } = voi;
|
const { windowWidth, windowCenter } = voi;
|
||||||
@ -168,7 +159,7 @@ function CornerstoneViewportOverlay({
|
|||||||
instanceNumber = _getInstanceNumberFromVolume(
|
instanceNumber = _getInstanceNumberFromVolume(
|
||||||
viewportData,
|
viewportData,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -183,15 +174,13 @@ function CornerstoneViewportOverlay({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [imageSliceData, viewportData, viewportIndex]);
|
}, [imageSliceData, viewportData, viewportId]);
|
||||||
|
|
||||||
if (!viewportData) {
|
if (!viewportData) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex(
|
const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ohifViewport) {
|
if (!ohifViewport) {
|
||||||
return null;
|
return null;
|
||||||
@ -201,9 +190,7 @@ function CornerstoneViewportOverlay({
|
|||||||
|
|
||||||
// Todo: probably this can be done in a better way in which we identify bright
|
// Todo: probably this can be done in a better way in which we identify bright
|
||||||
// background
|
// background
|
||||||
const isLight = backgroundColor
|
const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
|
||||||
? utilities.isEqual(backgroundColor, [1, 1, 1])
|
|
||||||
: false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewportOverlay
|
<ViewportOverlay
|
||||||
@ -240,7 +227,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
|||||||
function _getInstanceNumberFromVolume(
|
function _getInstanceNumberFromVolume(
|
||||||
viewportData,
|
viewportData,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
viewportIndex,
|
viewportId,
|
||||||
cornerstoneViewportService
|
cornerstoneViewportService
|
||||||
) {
|
) {
|
||||||
const volumes = viewportData.volumes;
|
const volumes = viewportData.volumes;
|
||||||
@ -253,9 +240,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
const volume = volumes[0];
|
const volume = volumes[0];
|
||||||
const { direction, imageIds } = volume;
|
const { direction, imageIds } = volume;
|
||||||
|
|
||||||
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cornerstoneViewport) {
|
if (!cornerstoneViewport) {
|
||||||
return;
|
return;
|
||||||
@ -278,8 +263,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { instanceNumber } =
|
const { instanceNumber } = metaData.get('generalImageModule', imageId) || {};
|
||||||
metaData.get('generalImageModule', imageId) || {};
|
|
||||||
return parseInt(instanceNumber);
|
return parseInt(instanceNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +271,7 @@ function _getInstanceNumberFromVolume(
|
|||||||
CornerstoneViewportOverlay.propTypes = {
|
CornerstoneViewportOverlay.propTypes = {
|
||||||
viewportData: PropTypes.object,
|
viewportData: PropTypes.object,
|
||||||
imageIndex: PropTypes.number,
|
imageIndex: PropTypes.number,
|
||||||
viewportIndex: PropTypes.number,
|
viewportId: PropTypes.string,
|
||||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
servicesManager: PropTypes.instanceOf(ServicesManager),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -75,22 +75,16 @@ export function formatPN(name) {
|
|||||||
* Gets compression type
|
* Gets compression type
|
||||||
*
|
*
|
||||||
* @param {number} imageId
|
* @param {number} imageId
|
||||||
* @returns {string} comrpession type.
|
* @returns {string} compression type.
|
||||||
*/
|
*/
|
||||||
export function getCompression(imageId) {
|
export function getCompression(imageId) {
|
||||||
const generalImageModule = metaData.get('generalImageModule', imageId) || {};
|
const generalImageModule = metaData.get('generalImageModule', imageId) || {};
|
||||||
const {
|
const { lossyImageCompression, lossyImageCompressionRatio, lossyImageCompressionMethod } =
|
||||||
lossyImageCompression,
|
generalImageModule;
|
||||||
lossyImageCompressionRatio,
|
|
||||||
lossyImageCompressionMethod,
|
|
||||||
} = generalImageModule;
|
|
||||||
|
|
||||||
if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
|
if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
|
||||||
const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
|
const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
|
||||||
const compressionRatio = formatNumberPrecision(
|
const compressionRatio = formatNumberPrecision(lossyImageCompressionRatio, 2);
|
||||||
lossyImageCompressionRatio,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
return compressionMethod + compressionRatio + ' : 1';
|
return compressionMethod + compressionRatio + ' : 1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,9 +56,7 @@ function commandsModule({
|
|||||||
// filter by allowed selected tools from config property (if there is any)
|
// filter by allowed selected tools from config property (if there is any)
|
||||||
const isToolAllowed =
|
const isToolAllowed =
|
||||||
!optionsToUse.allowedSelectedTools ||
|
!optionsToUse.allowedSelectedTools ||
|
||||||
optionsToUse.allowedSelectedTools.includes(
|
optionsToUse.allowedSelectedTools.includes(firstAnnotationSelected?.metadata?.toolName);
|
||||||
firstAnnotationSelected?.metadata?.toolName
|
|
||||||
);
|
|
||||||
if (isToolAllowed) {
|
if (isToolAllowed) {
|
||||||
optionsToUse.nearbyToolData = firstAnnotationSelected;
|
optionsToUse.nearbyToolData = firstAnnotationSelected;
|
||||||
} else {
|
} else {
|
||||||
@ -88,10 +86,7 @@ function commandsModule({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
|
getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
|
||||||
return (
|
return nearbyToolData ?? cstUtils.getAnnotationNearPoint(element, canvasCoordinates);
|
||||||
nearbyToolData ??
|
|
||||||
cstUtils.getAnnotationNearPoint(element, canvasCoordinates)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getNearbyAnnotation({ element, canvasCoordinates }) {
|
getNearbyAnnotation({ element, canvasCoordinates }) {
|
||||||
const nearbyToolData = actions.getNearbyToolData({
|
const nearbyToolData = actions.getNearbyToolData({
|
||||||
@ -108,18 +103,14 @@ function commandsModule({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { renderingEngineId, viewportId } = enabledElement;
|
const { renderingEngineId, viewportId } = enabledElement;
|
||||||
const toolGroup = ToolGroupManager.getToolGroupForViewport(
|
const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId, renderingEngineId);
|
||||||
viewportId,
|
|
||||||
renderingEngineId
|
|
||||||
);
|
|
||||||
|
|
||||||
const toolInstance = toolGroup.getToolInstance(toolName);
|
const toolInstance = toolGroup.getToolInstance(toolName);
|
||||||
|
|
||||||
return toolInstance?.constructor?.isAnnotation ?? true;
|
return toolInstance?.constructor?.isAnnotation ?? true;
|
||||||
};
|
};
|
||||||
|
|
||||||
return nearbyToolData?.metadata?.toolName &&
|
return nearbyToolData?.metadata?.toolName && isAnnotation(nearbyToolData.metadata.toolName)
|
||||||
isAnnotation(nearbyToolData.metadata.toolName)
|
|
||||||
? nearbyToolData
|
? nearbyToolData
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
@ -152,11 +143,7 @@ function commandsModule({
|
|||||||
label,
|
label,
|
||||||
});
|
});
|
||||||
|
|
||||||
measurementService.update(
|
measurementService.update(updatedMeasurement.uid, updatedMeasurement, true);
|
||||||
updatedMeasurement.uid,
|
|
||||||
updatedMeasurement,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@ -167,7 +154,7 @@ function commandsModule({
|
|||||||
* @param props - containing the updates to apply
|
* @param props - containing the updates to apply
|
||||||
* @param props.measurementKey - chooses the measurement key to apply the
|
* @param props.measurementKey - chooses the measurement key to apply the
|
||||||
* code to. This will typically be finding or site to apply a
|
* 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:
|
* @param props.code - A coding scheme value from DICOM, including:
|
||||||
* * CodeValue - the language independent code, for example '1234'
|
* * CodeValue - the language independent code, for example '1234'
|
||||||
* * CodingSchemeDesignator - the issue of the code value
|
* * CodingSchemeDesignator - the issue of the code value
|
||||||
@ -219,39 +206,49 @@ function commandsModule({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
measurementService.update(
|
measurementService.update(updatedMeasurement.uid, updatedMeasurement, true);
|
||||||
updatedMeasurement.uid,
|
|
||||||
updatedMeasurement,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Retrieve value commands
|
// Retrieve value commands
|
||||||
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,
|
getActiveViewportEnabledElement: _getActiveViewportEnabledElement,
|
||||||
|
|
||||||
setViewportActive: ({ viewportId }) => {
|
setViewportActive: ({ viewportId }) => {
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(
|
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||||
viewportId
|
|
||||||
);
|
|
||||||
if (!viewportInfo) {
|
if (!viewportInfo) {
|
||||||
console.warn('No viewport found for viewportId:', viewportId);
|
console.warn('No viewport found for viewportId:', viewportId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewportIndex = viewportInfo.getViewportIndex();
|
viewportGridService.setActiveViewportId(viewportId);
|
||||||
viewportGridService.setActiveViewportIndex(viewportIndex);
|
|
||||||
},
|
},
|
||||||
arrowTextCallback: ({ callback, data }) => {
|
arrowTextCallback: ({ callback, data }) => {
|
||||||
callInputDialog(uiDialogService, data, callback);
|
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: () => {
|
toggleCine: () => {
|
||||||
const { viewports } = viewportGridService.getState();
|
const { viewports } = viewportGridService.getState();
|
||||||
const { isCineEnabled } = cineService.getState();
|
const { isCineEnabled } = cineService.getState();
|
||||||
cineService.setIsCineEnabled(!isCineEnabled);
|
cineService.setIsCineEnabled(!isCineEnabled);
|
||||||
toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } });
|
toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } });
|
||||||
viewports.forEach((_, index) =>
|
viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false }));
|
||||||
cineService.setCine({ id: index, isPlaying: false })
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
setWindowLevel({ window, level, toolGroupId }) {
|
setWindowLevel({ window, level, toolGroupId }) {
|
||||||
// convert to numbers
|
// convert to numbers
|
||||||
@ -259,9 +256,7 @@ function commandsModule({
|
|||||||
const windowCenterNum = Number(level);
|
const windowCenterNum = Number(level);
|
||||||
|
|
||||||
const { viewportId } = _getActiveViewportEnabledElement();
|
const { viewportId } = _getActiveViewportEnabledElement();
|
||||||
const viewportToolGroupId = toolGroupService.getToolGroupForViewport(
|
const viewportToolGroupId = toolGroupService.getToolGroupForViewport(viewportId);
|
||||||
viewportId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (toolGroupId && toolGroupId !== viewportToolGroupId) {
|
if (toolGroupId && toolGroupId !== viewportToolGroupId) {
|
||||||
return;
|
return;
|
||||||
@ -271,10 +266,7 @@ function commandsModule({
|
|||||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
|
|
||||||
const { lower, upper } = csUtils.windowLevel.toLowHighRange(
|
const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum);
|
||||||
windowWidthNum,
|
|
||||||
windowCenterNum
|
|
||||||
);
|
|
||||||
|
|
||||||
viewport.setProperties({
|
viewport.setProperties({
|
||||||
voiRange: {
|
voiRange: {
|
||||||
@ -291,8 +283,12 @@ function commandsModule({
|
|||||||
toolbarServiceRecordInteraction: props => {
|
toolbarServiceRecordInteraction: props => {
|
||||||
toolbarService.recordInteraction(props);
|
toolbarService.recordInteraction(props);
|
||||||
},
|
},
|
||||||
|
// Enable or disable a toggleable command, without calling the activation
|
||||||
setToolActive: ({ toolName, toolGroupId = null }) => {
|
// 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') {
|
if (toolName === 'Crosshairs') {
|
||||||
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
||||||
|
|
||||||
@ -309,29 +305,15 @@ function commandsModule({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { viewports } = viewportGridService.getState() || {
|
const { viewports } = viewportGridService.getState();
|
||||||
viewports: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
if (!viewports.size) {
|
||||||
const toolGroupViewportIds = toolGroup?.getViewportIds?.();
|
|
||||||
|
|
||||||
// if toolGroup has been destroyed, or its viewports have been removed
|
|
||||||
if (!toolGroupViewportIds || !toolGroupViewportIds.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredViewports = viewports.filter(viewport => {
|
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||||
if (!viewport.viewportOptions) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toolGroupViewportIds.includes(
|
if (!toolGroup) {
|
||||||
viewport.viewportOptions.viewportId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!filteredViewports.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,6 +339,14 @@ function commandsModule({
|
|||||||
toolGroup.setToolPassive(activeToolName);
|
toolGroup.setToolPassive(activeToolName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is a toggle state, then simply set the enabled/disabled state without
|
||||||
|
// setting the tool active.
|
||||||
|
if (toggledState != null) {
|
||||||
|
toggledState ? toolGroup.setToolEnabled(toolName) : toolGroup.setToolDisabled(toolName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the new toolName to be active
|
// Set the new toolName to be active
|
||||||
toolGroup.setToolActive(toolName, {
|
toolGroup.setToolActive(toolName, {
|
||||||
bindings: [
|
bindings: [
|
||||||
@ -367,13 +357,9 @@ function commandsModule({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
showDownloadViewportModal: () => {
|
showDownloadViewportModal: () => {
|
||||||
const { activeViewportIndex } = viewportGridService.getState();
|
const { activeViewportId } = viewportGridService.getState();
|
||||||
|
|
||||||
if (
|
if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) {
|
||||||
!cornerstoneViewportService.getCornerstoneViewportByIndex(
|
|
||||||
activeViewportIndex
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Cannot download a non-cornerstone viewport (image).
|
// Cannot download a non-cornerstone viewport (image).
|
||||||
uiNotificationService.show({
|
uiNotificationService.show({
|
||||||
title: 'Download Image',
|
title: 'Download Image',
|
||||||
@ -390,7 +376,7 @@ function commandsModule({
|
|||||||
content: CornerstoneViewportDownloadForm,
|
content: CornerstoneViewportDownloadForm,
|
||||||
title: 'Download High Quality Image',
|
title: 'Download High Quality Image',
|
||||||
contentProps: {
|
contentProps: {
|
||||||
activeViewportIndex,
|
activeViewportId,
|
||||||
onClose: uiModalService.hide,
|
onClose: uiModalService.hide,
|
||||||
cornerstoneViewportService,
|
cornerstoneViewportService,
|
||||||
},
|
},
|
||||||
@ -457,11 +443,9 @@ function commandsModule({
|
|||||||
|
|
||||||
const { viewport } = enabledElement;
|
const { viewport } = enabledElement;
|
||||||
|
|
||||||
if (viewport instanceof StackViewport) {
|
const { invert } = viewport.getProperties();
|
||||||
const { invert } = viewport.getProperties();
|
viewport.setProperties({ invert: !invert });
|
||||||
viewport.setProperties({ invert: !invert });
|
viewport.render();
|
||||||
viewport.render();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resetViewport: () => {
|
resetViewport: () => {
|
||||||
const enabledElement = _getActiveViewportEnabledElement();
|
const enabledElement = _getActiveViewportEnabledElement();
|
||||||
@ -476,7 +460,7 @@ function commandsModule({
|
|||||||
viewport.resetProperties();
|
viewport.resetProperties();
|
||||||
viewport.resetCamera();
|
viewport.resetCamera();
|
||||||
} else {
|
} else {
|
||||||
// Todo: add reset properties for volume viewport
|
viewport.resetProperties();
|
||||||
viewport.resetCamera();
|
viewport.resetCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,9 +498,7 @@ function commandsModule({
|
|||||||
}
|
}
|
||||||
viewport = enabledElement.viewport;
|
viewport = enabledElement.viewport;
|
||||||
} else {
|
} else {
|
||||||
viewport = cornerstoneViewportService.getCornerstoneViewport(
|
viewport = cornerstoneViewportService.getCornerstoneViewport(gridViewport.id);
|
||||||
gridViewport.id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get number of slices
|
// Get number of slices
|
||||||
@ -526,14 +508,12 @@ function commandsModule({
|
|||||||
if (viewport instanceof StackViewport) {
|
if (viewport instanceof StackViewport) {
|
||||||
numberOfSlices = viewport.getImageIds().length;
|
numberOfSlices = viewport.getImageIds().length;
|
||||||
} else if (viewport instanceof VolumeViewport) {
|
} else if (viewport instanceof VolumeViewport) {
|
||||||
numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport)
|
numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices;
|
||||||
.numberOfSlices;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unsupported viewport type');
|
throw new Error('Unsupported viewport type');
|
||||||
}
|
}
|
||||||
|
|
||||||
const jumpIndex =
|
const jumpIndex = imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex;
|
||||||
imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex;
|
|
||||||
if (jumpIndex >= numberOfSlices || jumpIndex < 0) {
|
if (jumpIndex >= numberOfSlices || jumpIndex < 0) {
|
||||||
throw new Error(`Can't jump to ${imageIndex}`);
|
throw new Error(`Can't jump to ${imageIndex}`);
|
||||||
}
|
}
|
||||||
@ -554,15 +534,8 @@ function commandsModule({
|
|||||||
|
|
||||||
cstUtils.scroll(viewport, options);
|
cstUtils.scroll(viewport, options);
|
||||||
},
|
},
|
||||||
setViewportColormap: ({
|
setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, immediate = false }) => {
|
||||||
viewportIndex,
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
displaySetInstanceUID,
|
|
||||||
colormap,
|
|
||||||
immediate = false,
|
|
||||||
}) => {
|
|
||||||
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(
|
|
||||||
viewportIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
const actorEntries = viewport.getActors();
|
const actorEntries = viewport.getActors();
|
||||||
|
|
||||||
@ -578,37 +551,29 @@ function commandsModule({
|
|||||||
viewport.render();
|
viewport.render();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
incrementActiveViewport: () => {
|
changeActiveViewport: ({ direction = 1 }) => {
|
||||||
const { activeViewportIndex, viewports } = viewportGridService.getState();
|
const { activeViewportId, viewports } = viewportGridService.getState();
|
||||||
const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
|
const viewportIds = Array.from(viewports.keys());
|
||||||
viewportGridService.setActiveViewportIndex(nextViewportIndex);
|
const currentIndex = viewportIds.indexOf(activeViewportId);
|
||||||
},
|
|
||||||
decrementActiveViewport: () => {
|
|
||||||
const { activeViewportIndex, viewports } = viewportGridService.getState();
|
|
||||||
const nextViewportIndex =
|
const nextViewportIndex =
|
||||||
(activeViewportIndex - 1 + viewports.length) % viewports.length;
|
(currentIndex + direction + viewportIds.length) % viewportIds.length;
|
||||||
viewportGridService.setActiveViewportIndex(nextViewportIndex);
|
viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleStackImageSync: ({ toggledState }) => {
|
toggleStackImageSync: ({ toggledState }) => {
|
||||||
toggleStackImageSync({
|
toggleStackImageSync({
|
||||||
getEnabledElement,
|
|
||||||
servicesManager,
|
servicesManager,
|
||||||
toggledState,
|
toggledState,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleReferenceLines: ({ toggledState }) => {
|
setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => {
|
||||||
const { activeViewportIndex } = viewportGridService.getState();
|
if (!viewportId) {
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
|
const { activeViewportId } = viewportGridService.getState();
|
||||||
activeViewportIndex
|
viewportId = activeViewportId;
|
||||||
);
|
|
||||||
|
|
||||||
const viewportId = viewportInfo.getViewportId();
|
|
||||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
|
||||||
|
|
||||||
if (!toggledState) {
|
|
||||||
toolGroup.setToolDisabled(ReferenceLinesTool.toolName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||||
|
|
||||||
toolGroup.setToolConfiguration(
|
toolGroup.setToolConfiguration(
|
||||||
ReferenceLinesTool.toolName,
|
ReferenceLinesTool.toolName,
|
||||||
{
|
{
|
||||||
@ -616,7 +581,9 @@ function commandsModule({
|
|||||||
},
|
},
|
||||||
true // overwrite
|
true // overwrite
|
||||||
);
|
);
|
||||||
toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
|
},
|
||||||
|
storePresentation: ({ viewportId }) => {
|
||||||
|
cornerstoneViewportService.storePresentation({ viewportId });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -673,10 +640,11 @@ function commandsModule({
|
|||||||
options: { rotation: -90 },
|
options: { rotation: -90 },
|
||||||
},
|
},
|
||||||
incrementActiveViewport: {
|
incrementActiveViewport: {
|
||||||
commandFn: actions.incrementActiveViewport,
|
commandFn: actions.changeActiveViewport,
|
||||||
},
|
},
|
||||||
decrementActiveViewport: {
|
decrementActiveViewport: {
|
||||||
commandFn: actions.decrementActiveViewport,
|
commandFn: actions.changeActiveViewport,
|
||||||
|
options: { direction: -1 },
|
||||||
},
|
},
|
||||||
flipViewportHorizontal: {
|
flipViewportHorizontal: {
|
||||||
commandFn: actions.flipViewportHorizontal,
|
commandFn: actions.flipViewportHorizontal,
|
||||||
@ -739,8 +707,17 @@ function commandsModule({
|
|||||||
toggleStackImageSync: {
|
toggleStackImageSync: {
|
||||||
commandFn: actions.toggleStackImageSync,
|
commandFn: actions.toggleStackImageSync,
|
||||||
},
|
},
|
||||||
toggleReferenceLines: {
|
setSourceViewportForReferenceLinesTool: {
|
||||||
commandFn: actions.toggleReferenceLines,
|
commandFn: actions.setSourceViewportForReferenceLinesTool,
|
||||||
|
},
|
||||||
|
storePresentation: {
|
||||||
|
commandFn: actions.storePresentation,
|
||||||
|
},
|
||||||
|
setToolbarToggled: {
|
||||||
|
commandFn: actions.setToolbarToggled,
|
||||||
|
},
|
||||||
|
cleanUpCrosshairs: {
|
||||||
|
commandFn: actions.cleanUpCrosshairs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,97 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
|
||||||
|
import { Enums, eventTarget } from '@cornerstonejs/core';
|
||||||
|
|
||||||
|
function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
|
||||||
|
const { toolbarService, customizationService } = servicesManager.services;
|
||||||
|
const [{ isCineEnabled, cines }, cineService] = useCine();
|
||||||
|
const [{ activeViewportId }] = useViewportGrid();
|
||||||
|
|
||||||
|
const { component: CinePlayerComponent = CinePlayer } =
|
||||||
|
customizationService.get('cinePlayer') ?? {};
|
||||||
|
|
||||||
|
const handleCineClose = () => {
|
||||||
|
toolbarService.recordInteraction({
|
||||||
|
groupId: 'MoreTools',
|
||||||
|
interactionType: 'toggle',
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
commandName: 'toggleCine',
|
||||||
|
commandOptions: {},
|
||||||
|
toolName: 'cine',
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cineHandler = () => {
|
||||||
|
if (!cines || !cines[viewportId] || !enabledVPElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cine = cines[viewportId];
|
||||||
|
const isPlaying = cine.isPlaying || false;
|
||||||
|
const frameRate = cine.frameRate || 24;
|
||||||
|
|
||||||
|
const validFrameRate = Math.max(frameRate, 1);
|
||||||
|
|
||||||
|
if (isPlaying) {
|
||||||
|
cineService.playClip(enabledVPElement, {
|
||||||
|
framesPerSecond: validFrameRate,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cineService.stopClip(enabledVPElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cineService.setCine({ id: viewportId, isPlaying: false });
|
||||||
|
eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
|
||||||
|
};
|
||||||
|
}, [enabledVPElement]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cines || !cines[viewportId] || !enabledVPElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cineHandler();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (enabledVPElement && cines?.[viewportId]?.isPlaying) {
|
||||||
|
cineService.stopClip(enabledVPElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [cines, viewportId, cineService, enabledVPElement, cineHandler]);
|
||||||
|
|
||||||
|
const cine = cines[viewportId];
|
||||||
|
const isPlaying = (cine && cine.isPlaying) || false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
isCineEnabled && (
|
||||||
|
<CinePlayerComponent
|
||||||
|
className="absolute left-1/2 bottom-3 -translate-x-1/2"
|
||||||
|
isPlaying={isPlaying}
|
||||||
|
onClose={handleCineClose}
|
||||||
|
onPlayPauseChange={isPlaying =>
|
||||||
|
cineService.setCine({
|
||||||
|
id: activeViewportId,
|
||||||
|
isPlaying,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onFrameRateChange={frameRate =>
|
||||||
|
cineService.setCine({
|
||||||
|
id: activeViewportId,
|
||||||
|
frameRate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WrappedCinePlayer;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import CinePlayer from './CinePlayer';
|
||||||
|
|
||||||
|
export default CinePlayer;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user