Initial commit from prod-batam
This commit is contained in:
6
.browserslistrc
Normal file
6
.browserslistrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Browsers that we support
|
||||||
|
|
||||||
|
> 1%
|
||||||
|
IE 11
|
||||||
|
not dead
|
||||||
|
not op_mini all
|
||||||
554
.circleci/config.yml
Normal file
554
.circleci/config.yml
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
### ABOUT
|
||||||
|
#
|
||||||
|
# This configuration powers our Circleci.io integration
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# Netlify works independently from this configuration to
|
||||||
|
# create pull request previews and to update `https://docs.ohif.org`
|
||||||
|
###
|
||||||
|
|
||||||
|
## https://github.com/cypress-io/circleci-orb
|
||||||
|
##
|
||||||
|
orbs:
|
||||||
|
codecov: codecov/codecov@1.0.5
|
||||||
|
cypress: cypress-io/cypress@3.3.1
|
||||||
|
|
||||||
|
executors:
|
||||||
|
cypress-custom:
|
||||||
|
description: |
|
||||||
|
Single Docker container used to run Cypress Tests
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:<< parameters.node-version >>-browsers
|
||||||
|
parameters:
|
||||||
|
node-version:
|
||||||
|
default: '18.16.1'
|
||||||
|
description: |
|
||||||
|
The version of Node to run your tests with.
|
||||||
|
type: string
|
||||||
|
|
||||||
|
defaults: &defaults
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18
|
||||||
|
environment:
|
||||||
|
TERM: xterm # Enable colors in term
|
||||||
|
QUICK_BUILD: true
|
||||||
|
working_directory: ~/repo
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
###
|
||||||
|
# Workflow: PR_CHECKS
|
||||||
|
###
|
||||||
|
UNIT_TESTS:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
# Update yarn
|
||||||
|
- run: yarn -v
|
||||||
|
- run: node --version
|
||||||
|
# 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
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache ## Cache yarn and Cypress
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
# RUN TESTS
|
||||||
|
- run:
|
||||||
|
name: 'JavaScript Test Suite'
|
||||||
|
command: yarn run test:unit:ci
|
||||||
|
# platform/app
|
||||||
|
- run:
|
||||||
|
name: 'VIEWER: Combine report output'
|
||||||
|
command: |
|
||||||
|
viewerCov="/home/circleci/repo/platform/app/coverage"
|
||||||
|
touch "${viewerCov}/reports"
|
||||||
|
cat "${viewerCov}/clover.xml" >> "${viewerCov}/reports"
|
||||||
|
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
||||||
|
cat "${viewerCov}/lcov.info" >>"${viewerCov}/reports"
|
||||||
|
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
||||||
|
- codecov/upload:
|
||||||
|
file: '/home/circleci/repo/platform/app/coverage/reports'
|
||||||
|
flags: 'viewer'
|
||||||
|
# PLATFORM/CORE
|
||||||
|
- run:
|
||||||
|
name: 'CORE: Combine report output'
|
||||||
|
command: |
|
||||||
|
coreCov="/home/circleci/repo/platform/core/coverage"
|
||||||
|
touch "${coreCov}/reports"
|
||||||
|
cat "${coreCov}/clover.xml" >> "${coreCov}/reports"
|
||||||
|
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
||||||
|
cat "${coreCov}/lcov.info" >> "${coreCov}/reports"
|
||||||
|
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
||||||
|
- codecov/upload:
|
||||||
|
file: '/home/circleci/repo/platform/core/coverage/reports'
|
||||||
|
flags: 'core'
|
||||||
|
|
||||||
|
###
|
||||||
|
# Workflow: DEPLOY
|
||||||
|
###
|
||||||
|
BUILD:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
# 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
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache ## Cache yarn and Cypress
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
# Build & Test
|
||||||
|
- run:
|
||||||
|
name: 'Perform the versioning before build'
|
||||||
|
command: node ./version.mjs
|
||||||
|
- run:
|
||||||
|
name: 'Build the OHIF Viewer'
|
||||||
|
command: yarn run build
|
||||||
|
no_output_timeout: 45m
|
||||||
|
- run:
|
||||||
|
name: 'Upload SourceMaps, Send Deploy Notification'
|
||||||
|
command: |
|
||||||
|
# export FILE_1=$(find ./build/static/js -type f -name "2.*.js" -exec basename {} \;)
|
||||||
|
# export FILE_MAIN=$(find ./build/static/js -type f -name "main.*.js" -exec basename {} \;)
|
||||||
|
# export FILE_RUNTIME_MAIN=$(find ./build/static/js -type f -name "runtime~main.*.js" -exec basename {} \;)
|
||||||
|
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_1.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_1
|
||||||
|
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_MAIN
|
||||||
|
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_RUNTIME_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_RUNTIME_MAIN
|
||||||
|
curl --request POST https://api.rollbar.com/api/1/deploy/ -F access_token=$ROLLBAR_TOKEN -F environment=$GOOGLE_STORAGE_BUCKET -F revision=$CIRCLE_SHA1 -F local_username=CircleCI
|
||||||
|
# Persist :+1:
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: ~/repo
|
||||||
|
paths:
|
||||||
|
- platform/app/dist
|
||||||
|
- Dockerfile
|
||||||
|
- version.txt
|
||||||
|
- commit.txt
|
||||||
|
- version.json
|
||||||
|
|
||||||
|
# just to make sure later on we can publish them
|
||||||
|
BUILD_PACKAGES_QUICK:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- run: yarn -v
|
||||||
|
# Checkout code and ALL Git Tags
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/repo
|
||||||
|
# Use increasingly general patterns to restore cache
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Yarn and Cypress Package Cache
|
||||||
|
keys:
|
||||||
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- yarn-packages-
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: yarn install --frozen-lockfile
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- run:
|
||||||
|
name: Avoid hosts unknown for github
|
||||||
|
command: |
|
||||||
|
rm -rf ~/.ssh
|
||||||
|
mkdir ~/.ssh/
|
||||||
|
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
|
||||||
|
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
||||||
|
git config --global user.name "ohif-bot"
|
||||||
|
- run:
|
||||||
|
name: Authenticate with NPM registry
|
||||||
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
|
- run:
|
||||||
|
name: build half of the packages (to avoid out of memory in circleci)
|
||||||
|
command: |
|
||||||
|
yarn run build:package-all
|
||||||
|
- run:
|
||||||
|
name: build the other half of the packages
|
||||||
|
command: |
|
||||||
|
yarn run build:package-all-1
|
||||||
|
|
||||||
|
###
|
||||||
|
# Workflow: RELEASE
|
||||||
|
###
|
||||||
|
NPM_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- run: yarn -v
|
||||||
|
# Checkout code and ALL Git Tags
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/repo
|
||||||
|
# Use increasingly general patterns to restore cache
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Yarn and Cypress Package Cache
|
||||||
|
keys:
|
||||||
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- yarn-packages-
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: yarn install --frozen-lockfile
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- run:
|
||||||
|
name: Avoid hosts unknown for github
|
||||||
|
command: |
|
||||||
|
rm -rf ~/.ssh
|
||||||
|
mkdir ~/.ssh/
|
||||||
|
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
|
||||||
|
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
||||||
|
git config --global user.name "ohif-bot"
|
||||||
|
- run:
|
||||||
|
name: Authenticate with NPM registry
|
||||||
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
|
- run:
|
||||||
|
name: build half of the packages (to avoid out of memory in circleci)
|
||||||
|
command: |
|
||||||
|
yarn run build:package-all
|
||||||
|
- run:
|
||||||
|
name: build the other half of the packages
|
||||||
|
command: |
|
||||||
|
yarn run build:package-all-1
|
||||||
|
- run:
|
||||||
|
name: increase min time out
|
||||||
|
command: |
|
||||||
|
npm config set fetch-retry-mintimeout 20000
|
||||||
|
- run:
|
||||||
|
name: increase max time out
|
||||||
|
command: |
|
||||||
|
npm config set fetch-retry-maxtimeout 120000
|
||||||
|
- run:
|
||||||
|
name: publish package versions
|
||||||
|
command: |
|
||||||
|
node ./publish-version.mjs
|
||||||
|
- run:
|
||||||
|
name: Again set the NPM registry (was deleted in the version script)
|
||||||
|
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
|
- run:
|
||||||
|
name: publish package dist
|
||||||
|
command: |
|
||||||
|
node ./publish-package.mjs
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: ~/repo
|
||||||
|
paths:
|
||||||
|
- .
|
||||||
|
|
||||||
|
DOCKER_RELEASE_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/repo
|
||||||
|
- setup_remote_docker:
|
||||||
|
docker_layer_caching: false
|
||||||
|
- run:
|
||||||
|
name: Build and push Docker image from the release branch
|
||||||
|
command: |
|
||||||
|
# This file will exist if a new version was published by
|
||||||
|
# our command in the previous job.
|
||||||
|
if [[ ! -e version.txt ]]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
# Remove npm config
|
||||||
|
rm -f ./.npmrc
|
||||||
|
# Set our version number using vars
|
||||||
|
export IMAGE_VERSION=$(cat version.txt)
|
||||||
|
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
|
||||||
|
echo $IMAGE_VERSION
|
||||||
|
echo $IMAGE_VERSION_FULL
|
||||||
|
# Build our image, auth, and push
|
||||||
|
docker build --tag ohif/app:$IMAGE_VERSION_FULL --tag ohif/app:latest .
|
||||||
|
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||||
|
docker push ohif/app:$IMAGE_VERSION_FULL
|
||||||
|
docker push ohif/app:latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_BETA_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/repo
|
||||||
|
- setup_remote_docker:
|
||||||
|
docker_layer_caching: false
|
||||||
|
- run:
|
||||||
|
name: Build and push Docker image from the master branch (beta releases)
|
||||||
|
command: |
|
||||||
|
echo $(ls -l)
|
||||||
|
|
||||||
|
# This file will exist if a new version was published by
|
||||||
|
# our command in the previous job.
|
||||||
|
if [[ ! -e version.txt ]]; then
|
||||||
|
echo "don't have version txt"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Building and pushing Docker image from the master branch (beta releases)"
|
||||||
|
rm -f ./.npmrc
|
||||||
|
# Set our version number using vars
|
||||||
|
export IMAGE_VERSION=$(cat version.txt)
|
||||||
|
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
|
||||||
|
echo $IMAGE_VERSION
|
||||||
|
echo $IMAGE_VERSION_FULL
|
||||||
|
# Build our image, auth, and push
|
||||||
|
|
||||||
|
echo "starting docker build"
|
||||||
|
docker build --tag ohif/app:$IMAGE_VERSION_FULL .
|
||||||
|
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||||
|
|
||||||
|
echo "starting docker push"
|
||||||
|
docker push ohif/app:$IMAGE_VERSION_FULL
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This is copied from the Cypress orb since the default for cypress/run is node 16 and
|
||||||
|
# we migrated to 18
|
||||||
|
CYPRESS_CUSTOM_RUN:
|
||||||
|
description: |
|
||||||
|
A single, complete job to run Cypress end-to-end tests in your application.
|
||||||
|
executor: cypress-custom
|
||||||
|
parallelism: << parameters.parallelism >>
|
||||||
|
parameters:
|
||||||
|
cypress-cache-key:
|
||||||
|
default: cypress-cache-{{ arch }}-{{ checksum "package.json" }}
|
||||||
|
description: Cache key used to cache the Cypress binary.
|
||||||
|
type: string
|
||||||
|
cypress-cache-path:
|
||||||
|
default: ~/.cache/Cypress
|
||||||
|
description: |
|
||||||
|
By default, this will cache the '~/.cache/Cypress' directory so that the Cypress binary is cached. You can override this by providing your own cache path.
|
||||||
|
type: string
|
||||||
|
cypress-command:
|
||||||
|
default: npx cypress run
|
||||||
|
description: Command used to run your Cypress tests
|
||||||
|
type: string
|
||||||
|
include-branch-in-node-cache-key:
|
||||||
|
default: false
|
||||||
|
description: |
|
||||||
|
If true, this cache will only apply to runs within the same branch. (Adds -{{ .Branch }}- to the node cache key)
|
||||||
|
type: boolean
|
||||||
|
install-browsers:
|
||||||
|
default: false
|
||||||
|
description: |
|
||||||
|
Cypress runs by default in the Electron browser. Use this flag to install additional browsers to run your tests in.
|
||||||
|
This is only needed if you are passing the `--browser` flag in your `cypress-command`.
|
||||||
|
This parameter leverages the `circleci/browser-tools` orb and includes Chrome and FireFox.
|
||||||
|
If you need additional browser support you can set this to false and use an executor with a docker image
|
||||||
|
that includes the browsers of your choosing. See https://hub.docker.com/r/cypress/browsers/tags
|
||||||
|
type: boolean
|
||||||
|
install-command:
|
||||||
|
default: ''
|
||||||
|
description: Overrides the default NPM command (npm ci)
|
||||||
|
type: string
|
||||||
|
node-cache-version:
|
||||||
|
default: v1
|
||||||
|
description:
|
||||||
|
Change the default node cache version if you need to clear the cache for any reason.
|
||||||
|
type: string
|
||||||
|
package-manager:
|
||||||
|
default: npm
|
||||||
|
description: Select the default node package manager to use. NPM v5+ Required.
|
||||||
|
enum:
|
||||||
|
- npm
|
||||||
|
- yarn
|
||||||
|
- yarn-berry
|
||||||
|
type: enum
|
||||||
|
parallelism:
|
||||||
|
default: 4
|
||||||
|
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
|
||||||
|
resource_class: large
|
||||||
|
steps:
|
||||||
|
- cypress/install:
|
||||||
|
cypress-cache-key: << parameters.cypress-cache-key >>
|
||||||
|
cypress-cache-path: << parameters.cypress-cache-path >>
|
||||||
|
include-branch-in-node-cache-key: << parameters.include-branch-in-node-cache-key >>
|
||||||
|
install-browsers: << parameters.install-browsers >>
|
||||||
|
install-command: << parameters.install-command >>
|
||||||
|
node-cache-version: << parameters.node-cache-version >>
|
||||||
|
package-manager: << parameters.package-manager >>
|
||||||
|
post-install: << parameters.post-install >>
|
||||||
|
working-directory: << parameters.working-directory >>
|
||||||
|
- cypress/run-tests:
|
||||||
|
cypress-command: << parameters.cypress-command >>
|
||||||
|
start-command: << parameters.start-command >>
|
||||||
|
working-directory: << parameters.working-directory >>
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
PR_CHECKS:
|
||||||
|
jobs:
|
||||||
|
- BUILD_PACKAGES_QUICK:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
ignore: master
|
||||||
|
- UNIT_TESTS:
|
||||||
|
requires:
|
||||||
|
- BUILD_PACKAGES_QUICK
|
||||||
|
- CYPRESS_CUSTOM_RUN:
|
||||||
|
name: 'Cypress Tests'
|
||||||
|
context: cypress
|
||||||
|
matrix:
|
||||||
|
parameters:
|
||||||
|
start-command:
|
||||||
|
- yarn run test:data && yarn run test:e2e:serve
|
||||||
|
install-browsers:
|
||||||
|
- true
|
||||||
|
cypress-command:
|
||||||
|
- 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run
|
||||||
|
--record --browser chrome --parallel'
|
||||||
|
package-manager:
|
||||||
|
- 'yarn'
|
||||||
|
cypress-cache-key:
|
||||||
|
- 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
|
cypress-cache-path:
|
||||||
|
- '~/.cache/Cypress'
|
||||||
|
requires:
|
||||||
|
- BUILD_PACKAGES_QUICK
|
||||||
|
|
||||||
|
# PR_OPTIONAL_VISUAL_TESTS:
|
||||||
|
# jobs:
|
||||||
|
# - AWAIT_APPROVAL:
|
||||||
|
# type: approval
|
||||||
|
# # Update hub.docker.org
|
||||||
|
# - cypress/run:
|
||||||
|
# name: 'Generate Percy Snapshots'
|
||||||
|
# executor: cypress/browsers-chrome76
|
||||||
|
# browser: chrome
|
||||||
|
# pre-steps:
|
||||||
|
# - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
|
||||||
|
# yarn: true
|
||||||
|
# store_artifacts: false
|
||||||
|
# working_directory: platform/app
|
||||||
|
# build:
|
||||||
|
# yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
|
||||||
|
# yarn run build
|
||||||
|
# # start server --> verify running --> percy + chrome + cypress
|
||||||
|
# command: yarn run test:e2e:dist
|
||||||
|
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
|
# no-workspace: true # Don't persist workspace
|
||||||
|
# post-steps:
|
||||||
|
# - store_artifacts:
|
||||||
|
# path: platform/app/cypress/screenshots
|
||||||
|
# - store_artifacts:
|
||||||
|
# path: platform/app/cypress/videos
|
||||||
|
# requires:
|
||||||
|
# - AWAIT_APPROVAL
|
||||||
|
|
||||||
|
# Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is
|
||||||
|
# deployed from the release branch which is more stable and less frequently updated.
|
||||||
|
DEPLOY_MASTER:
|
||||||
|
jobs:
|
||||||
|
- BUILD:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
|
- NPM_PUBLISH:
|
||||||
|
requires:
|
||||||
|
- BUILD
|
||||||
|
- DOCKER_BETA_PUBLISH:
|
||||||
|
requires:
|
||||||
|
- NPM_PUBLISH
|
||||||
|
|
||||||
|
# Our release branch deploys to viewer.ohif.org and is more stable and less
|
||||||
|
# frequently updated after being tested in the staging environment.
|
||||||
|
DEPLOY_RELEASE:
|
||||||
|
jobs:
|
||||||
|
- BUILD:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: /^release\/.*/
|
||||||
|
- HOLD_FOR_APPROVAL:
|
||||||
|
type: approval
|
||||||
|
requires:
|
||||||
|
- BUILD
|
||||||
|
- NPM_PUBLISH:
|
||||||
|
requires:
|
||||||
|
- HOLD_FOR_APPROVAL
|
||||||
|
- DOCKER_RELEASE_PUBLISH:
|
||||||
|
requires:
|
||||||
|
- NPM_PUBLISH
|
||||||
|
|
||||||
|
###
|
||||||
|
# Unit and E2E tests have already run for PR_CHECKS
|
||||||
|
# Re-running should not gain us any confidence here
|
||||||
|
###
|
||||||
|
# RELEASE:
|
||||||
|
# jobs:
|
||||||
|
# - NPM_PUBLISH:
|
||||||
|
# filters:
|
||||||
|
# branches:
|
||||||
|
# only: master
|
||||||
|
# - DOCS_PUBLISH:
|
||||||
|
# filters:
|
||||||
|
# branches:
|
||||||
|
# only: master
|
||||||
|
# # Update base branch snapshots
|
||||||
|
# # and record a Cypress dashboard test run
|
||||||
|
# - cypress/run:
|
||||||
|
# name: 'Generate Percy Snapshots'
|
||||||
|
# executor: cypress/browsers-chrome76
|
||||||
|
# browser: chrome
|
||||||
|
# pre-steps:
|
||||||
|
# - run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||||
|
# add wait-on' # Use yarn latest
|
||||||
|
# yarn: true
|
||||||
|
# store_artifacts: false
|
||||||
|
# working_directory: platform/app
|
||||||
|
# build:
|
||||||
|
# npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.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
|
||||||
|
# - store_test_results:
|
||||||
|
# path: platform/app/cypress/results
|
||||||
|
# filters:
|
||||||
|
# branches:
|
||||||
|
# only: master
|
||||||
|
# - DOCKER_MASTER_PUBLISH:
|
||||||
|
# requires:
|
||||||
|
# - NPM_PUBLISH
|
||||||
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
|
||||||
43
.docker/README.md
Normal file
43
.docker/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Docker compose files
|
||||||
|
|
||||||
|
This folder contains docker-compose files used to spin up OHIF-Viewer with
|
||||||
|
different options such as locally or with any PAS you desire to
|
||||||
|
|
||||||
|
## Public Server
|
||||||
|
|
||||||
|
## Local Orthanc
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
`$ docker-compose -f docker-compose-orthanc.yml build`
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
Starts containers and leaves them running in the background.
|
||||||
|
|
||||||
|
`$ docker-compose -f docker-compose-orthanc.yml up -d`
|
||||||
|
|
||||||
|
then, access the application at [http://localhost](http://localhost)
|
||||||
|
|
||||||
|
**remember that you have to access orthanc application and include your studies
|
||||||
|
there**
|
||||||
|
|
||||||
|
## Local Dcm4chee
|
||||||
|
|
||||||
|
#### build
|
||||||
|
|
||||||
|
`$ docker-compose -f docker-compose-dcm4chee.yml build`
|
||||||
|
|
||||||
|
#### run
|
||||||
|
|
||||||
|
`$ docker-compose -f docker-compose-dcm4chee.yml up -d`
|
||||||
|
|
||||||
|
then, access the application at [http://localhost](http://localhost)
|
||||||
|
|
||||||
|
**remember that you have to access dcm4chee application and include your studies
|
||||||
|
there** You can use the following command to import your studies into dcm4che
|
||||||
|
|
||||||
|
`$ docker run -v {YOUR_STUDY_FOLDER}:/tmp --rm --network=docker_dcm4che_default dcm4che/dcm4che-tools:5.14.0 storescu -cDCM4CHEE@arc:11112 /tmp`
|
||||||
|
|
||||||
|
**make sure that your Docker network name is docker_dcm4chee_default or change
|
||||||
|
it to the right one**
|
||||||
20
.docker/Viewer-v3.x/default.conf.template
Normal file
20
.docker/Viewer-v3.x/default.conf.template
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
server {
|
||||||
|
listen ${PORT} default_server;
|
||||||
|
listen [::]:${PORT} default_server;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
.docker/Viewer-v3.x/entrypoint.sh
Normal file
45
.docker/Viewer-v3.x/entrypoint.sh
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
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" ]
|
||||||
|
then
|
||||||
|
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
||||||
|
if [ -n "$CLIENT_ID" ]
|
||||||
|
then
|
||||||
|
echo "Google Cloud Healthcare \$CLIENT_ID has been provided: "
|
||||||
|
echo "$CLIENT_ID"
|
||||||
|
echo "Updating config..."
|
||||||
|
|
||||||
|
# - Use SED to replace the CLIENT_ID that is currently in google.js
|
||||||
|
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If HEALTHCARE_API_ENDPOINT is specified, use the google.js configuration with the modified endpoint
|
||||||
|
if [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||||
|
then
|
||||||
|
echo "Google Cloud Healthcare \$HEALTHCARE_API_ENDPOINT has been provided: "
|
||||||
|
echo "$HEALTHCARE_API_ENDPOINT"
|
||||||
|
echo "Updating config..."
|
||||||
|
|
||||||
|
# - Use SED to replace the HEALTHCARE_API_ENDPOINT that is currently in google.js
|
||||||
|
sed -i -e "s+https://healthcare.googleapis.com/v1+$HEALTHCARE_API_ENDPOINT+g" /usr/share/nginx/html/google.js
|
||||||
|
fi
|
||||||
|
|
||||||
|
# - Copy google.js to overwrite app-config.js
|
||||||
|
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
37
.dockerignore
Normal file
37
.dockerignore
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Reduces size of context and hides
|
||||||
|
# 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
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Root
|
||||||
|
README.md
|
||||||
|
Dockerfile
|
||||||
|
dockerfile
|
||||||
|
|
||||||
|
# Misc. Config
|
||||||
|
.git
|
||||||
|
.DS_Store
|
||||||
|
.gitignore
|
||||||
|
.vscode
|
||||||
|
.circleci
|
||||||
|
|
||||||
|
# Unnecessary things to pull into container
|
||||||
|
.circleci/
|
||||||
|
.github/
|
||||||
|
.netlify/
|
||||||
|
.scripts/
|
||||||
|
.vscode/
|
||||||
|
coverage/
|
||||||
|
docs/
|
||||||
|
testdata/
|
||||||
4
.eslintignore
Normal file
4
.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
config/**
|
||||||
|
docs/**
|
||||||
|
img/**
|
||||||
|
node_modules
|
||||||
30
.eslintrc.json
Normal file
30
.eslintrc.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["@typescript-eslint", "import", "eslint-plugin-tsdoc", "prettier"],
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
// Enforce consistent brace style for all control statements for readability
|
||||||
|
"curly": "error"
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"cy": true,
|
||||||
|
"before": true,
|
||||||
|
"context": true,
|
||||||
|
"Cypress": true,
|
||||||
|
"assert": true
|
||||||
|
}
|
||||||
|
}
|
||||||
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Set the default behavior,
|
||||||
|
# in case people don't have core.autocrlf set.
|
||||||
|
* text=auto
|
||||||
|
# Declares that files will always have CRLF line ends
|
||||||
|
*.sh text eol=lf
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: https://giving.massgeneral.org/ohif
|
||||||
84
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: 'Bug report'
|
||||||
|
description: Create a report to help us improve
|
||||||
|
title: '[Bug] '
|
||||||
|
labels: ['Community: Report :bug:', 'Awaiting Reproduction']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
👋 Hello, and thank you for contributing to our project! Your support is greatly appreciated.
|
||||||
|
|
||||||
|
🔍 Before proceeding, please make sure to read our [Rules of Conduct](https://github.com/OHIF/Viewers/blob/master/CODE_OF_CONDUCT.md) and familiarize yourself with our [development process](https:/docs.ohif.org/development/our-process).
|
||||||
|
|
||||||
|
❓ If you're here to seek general support or ask a question, we encourage you to visit our [community discussion board](https://community.ohif.org/)
|
||||||
|
|
||||||
|
🐞 For bug reports, please complete the following template in as much detail as possible. This will help us reproduce and address the issue efficiently.
|
||||||
|
|
||||||
|
🧪 Finally, ensure that you're using the latest version of the software and check if your issue has already been reported to avoid duplicates.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: bug_description
|
||||||
|
attributes:
|
||||||
|
label: Describe the Bug
|
||||||
|
description: 'A clear and concise description of what the bug is.'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction_steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: 'Please describe the steps to reproduce the issue.'
|
||||||
|
placeholder: "1. First step\n2. Second step\n3. ..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: current_behavior
|
||||||
|
attributes:
|
||||||
|
label: The current behavior
|
||||||
|
description:
|
||||||
|
'A clear and concise description of what happens instead of the expected behavior.'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected_behavior
|
||||||
|
attributes:
|
||||||
|
label: The expected behavior
|
||||||
|
description: 'A clear and concise description of what you expected to happen.'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: 'OS'
|
||||||
|
description: 'Your operating system.'
|
||||||
|
placeholder: 'e.g., Windows 10, macOS 10.15.4'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: node-version
|
||||||
|
attributes:
|
||||||
|
label: 'Node version'
|
||||||
|
description: 'Your Node.js version.'
|
||||||
|
placeholder: 'e.g., 18.16.1'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: browser
|
||||||
|
attributes:
|
||||||
|
label: 'Browser'
|
||||||
|
description: 'Your browser.'
|
||||||
|
placeholder: 'e.g., Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >
|
||||||
|
> :warning: Reports we cannot reproduce are at risk of being marked stale and > closed. The
|
||||||
|
more information you can provide, the more likely we are to look > into and address your
|
||||||
|
issue.
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 🤗 Support Question
|
||||||
|
url: https://community.ohif.org/
|
||||||
|
about: Please use our forum if you have questions or need help.
|
||||||
34
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Create a feature request
|
||||||
|
labels: ['Community: Request :hand:']
|
||||||
|
title: '[Feature Request] '
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
👋 Hello and thank you for your interest in our project!
|
||||||
|
|
||||||
|
🔍 Before you proceed, please read our [Rules of Conduct](https://github.com/OHIF/Viewers/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
🚀 If your request is specific to your needs, consider contributing it yourself! Read our [contributing guides](https://docs.ohif.org/development/contributing) to get started.
|
||||||
|
|
||||||
|
🖊️ Please provide as much detail as possible for your feature request. Mock-up screenshots, workflow or logic flow diagrams are very helpful. Discuss how your requested feature would interact with existing features.
|
||||||
|
|
||||||
|
⏱️ Lastly, tell us why we should prioritize your feature. What impact would it have?
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'What feature or change would you like to see made?'
|
||||||
|
description:
|
||||||
|
'Please include as much detail as possible including possibly mock up screen shots, workflow
|
||||||
|
or logic flow diagrams etc.'
|
||||||
|
placeholder: '...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 'Why should we prioritize this feature?'
|
||||||
|
description: 'Discuss if and how the requested feature interacts with existing features.'
|
||||||
|
placeholder: '...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
93
.github/pull_request_template.md
vendored
Normal file
93
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<!-- Do Not Delete This! pr_template -->
|
||||||
|
<!-- Please read our Rules of Conduct: https://github.com/OHIF/Viewers/blob/master/CODE_OF_CONDUCT.md -->
|
||||||
|
<!-- 🕮 Read our guide about our Contributing Guide here https://docs.ohif.org/development/contributing -->
|
||||||
|
<!-- :hand: Thank you for starting this amazing contribution! -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
⚠️⚠️ Please make sure the checklist section below is complete before submitting your PR.
|
||||||
|
To complete the checklist, add an 'x' to each item: [] -> [x]
|
||||||
|
(PRs that do not have all the checkboxes marked will not be approved)
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Provide a clear explanation of the reasoning behind this change, such as:
|
||||||
|
- A link to the issue being addressed, using the format "Fixes #ISSUE_NUMBER"
|
||||||
|
- An image showing the issue or problem being addressed (if not already in the issue)
|
||||||
|
- Error logs or callStacks to help with the understanding of the problem (if not already in the issue)
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Changes & Results
|
||||||
|
|
||||||
|
<!--
|
||||||
|
List all the changes that have been done, such as:
|
||||||
|
- Add new components
|
||||||
|
- Remove old components
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
What are the effects of this change?
|
||||||
|
- Before vs After
|
||||||
|
- Screenshots / GIFs / Videos
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe how we can test your changes.
|
||||||
|
- open a URL
|
||||||
|
- visit a page
|
||||||
|
- click on a button
|
||||||
|
- etc.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
#### PR
|
||||||
|
|
||||||
|
<!--
|
||||||
|
https://semantic-release.gitbook.io/semantic-release/#how-does-it-work
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Please note the letter casing in the provided examples (upper or lower).
|
||||||
|
|
||||||
|
- feat(MeasurementService): add ...
|
||||||
|
- fix(Toolbar): fix ...
|
||||||
|
- docs(Readme): update ...
|
||||||
|
- style(Whitespace): fix ...
|
||||||
|
- refactor(ExtensionManager): ...
|
||||||
|
- test(HangingProtocol): Add test ...
|
||||||
|
- chore(git): update ...
|
||||||
|
- perf(VolumeLoader): ...
|
||||||
|
|
||||||
|
You don't need to have each commit within the Pull Request follow the rule,
|
||||||
|
but the PR title must comply with it, as it will be used as the commit message
|
||||||
|
after the commits are squashed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [] My Pull Request title is descriptive, accurate and follows the
|
||||||
|
semantic-release format and guidelines.
|
||||||
|
|
||||||
|
#### Code
|
||||||
|
|
||||||
|
- [] My code has been well-documented (function documentation, inline comments,
|
||||||
|
etc.)
|
||||||
|
|
||||||
|
#### Public Documentation Updates
|
||||||
|
|
||||||
|
<!-- https://docs.ohif.org/ -->
|
||||||
|
|
||||||
|
- [] The documentation page has been updated as necessary for any public API
|
||||||
|
additions or removals.
|
||||||
|
|
||||||
|
#### Tested Environment
|
||||||
|
|
||||||
|
- [] OS: <!--[e.g. Windows 10, macOS 10.15.4]-->
|
||||||
|
- [] Node version: <!--[e.g. 18.16.1]-->
|
||||||
|
- [] Browser:
|
||||||
|
<!--[e.g. Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1]-->
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
[blog]: https://circleci.com/blog/triggering-trusted-ci-jobs-on-untrusted-forks/
|
||||||
|
[script]: https://github.com/jklukas/git-push-fork-to-upstream-branch
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
25
.github/stale.yml
vendored
Normal file
25
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# GitHub App: Stale
|
||||||
|
# https://github.com/apps/stale
|
||||||
|
#
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 180
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 60
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- 'Bug: Verified :bug:'
|
||||||
|
- 'PR: Awaiting Review 👀'
|
||||||
|
- 'Announcement 🎉'
|
||||||
|
- 'IDC:priority'
|
||||||
|
- 'IDC:candidate'
|
||||||
|
- 'IDC:collaboration'
|
||||||
|
- 'Community: Request :hand:'
|
||||||
|
- 'Community: Report :bug:'
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: 'Stale :baguette_bread:'
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had recent activity. It will
|
||||||
|
be closed if no further activity occurs. Thank you for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
65
.github/workflows/playwright.yml
vendored
Normal file
65
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Playwright Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
jobs:
|
||||||
|
playwright-tests:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
shardIndex: [1, 2, 3, 4, 5]
|
||||||
|
shardTotal: [5]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run:
|
||||||
|
export NODE_OPTIONS="--max_old_space_size=8192" && npx playwright test --shard=${{
|
||||||
|
matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||||
|
|
||||||
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: blob-report-${{ matrix.shardIndex }}
|
||||||
|
path: blob-report
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
merge-reports:
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
needs: [playwright-tests]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Download blob reports from GitHub Actions Artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: all-blob-reports
|
||||||
|
pattern: blob-report-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Merge into HTML Report
|
||||||
|
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||||
|
|
||||||
|
- name: Upload HTML report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: html-report--attempt-${{ github.run_attempt }}
|
||||||
|
path: playwright-report
|
||||||
|
retention-days: 14
|
||||||
62
.gitignore
vendored
Normal file
62
.gitignore
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Packages
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
docs/_book
|
||||||
|
src/version.js
|
||||||
|
junit.xml
|
||||||
|
coverage/
|
||||||
|
.docz/
|
||||||
|
.yarn/
|
||||||
|
.nx/
|
||||||
|
addOns/yarn.lock
|
||||||
|
|
||||||
|
# YALC (for Erik)
|
||||||
|
.yalc
|
||||||
|
yalc.lock
|
||||||
|
*.dcm
|
||||||
|
# Logging, System files, misc.
|
||||||
|
.idea/
|
||||||
|
.npm
|
||||||
|
npm-debug.log
|
||||||
|
package-lock.json
|
||||||
|
yarn-error.log
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
*.code-workspace
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Common Example Data Directories
|
||||||
|
sampledata/
|
||||||
|
example/deps/
|
||||||
|
docker/dcm4che/dcm4che-arc
|
||||||
|
|
||||||
|
# Cypress test results
|
||||||
|
videos/
|
||||||
|
|
||||||
|
|
||||||
|
# Locize settings
|
||||||
|
.locize
|
||||||
|
|
||||||
|
# autogenerated files
|
||||||
|
platform/app/src/pluginImports.js
|
||||||
|
/Viewers.iml
|
||||||
|
platform/app/.recipes/Nginx-Dcm4Chee/logs/*
|
||||||
|
platform/app/.recipes/OpenResty-Orthanc/logs/*
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
.vs
|
||||||
|
|
||||||
|
# PlayWright
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
tests/test-results/
|
||||||
|
tests/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|
||||||
|
# Dummy
|
||||||
|
/dump
|
||||||
|
platform/app/dist.zip
|
||||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "testdata"]
|
||||||
|
path = testdata
|
||||||
|
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
|
||||||
|
branch = main
|
||||||
32
.netlify/build-deploy-preview.sh
Executable file
32
.netlify/build-deploy-preview.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set directory to location of this script
|
||||||
|
# https://stackoverflow.com/a/3355423/1867984
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
cd .. # Up to project root
|
||||||
|
|
||||||
|
# Helpful to verify which versions we're using
|
||||||
|
echo 'My yarn version is... '
|
||||||
|
|
||||||
|
yarn -v
|
||||||
|
node -v
|
||||||
|
|
||||||
|
# Build && Move PWA Output
|
||||||
|
yarn run build:ci
|
||||||
|
mkdir -p ./.netlify/www/pwa
|
||||||
|
mv platform/app/dist/* .netlify/www/pwa -v
|
||||||
|
echo 'Web application built and copied'
|
||||||
|
|
||||||
|
# Build && Move Docusaurus Output (for the docs themselves)
|
||||||
|
cd platform/docs
|
||||||
|
yarn install
|
||||||
|
yarn run build
|
||||||
|
cd ../..
|
||||||
|
mkdir -p ./.netlify/www/docs
|
||||||
|
mv platform/docs/build/* .netlify/www/docs -v
|
||||||
|
echo 'Docs built (docusaurus) and copied'
|
||||||
|
|
||||||
|
# Cache all of the node_module dependencies in
|
||||||
|
# extensions, modules, and platform packages
|
||||||
|
yarn run lerna:cache
|
||||||
|
echo 'Nothing left to see here. Go home, folks.'
|
||||||
5
.netlify/deploy-workflow/_redirects
Normal file
5
.netlify/deploy-workflow/_redirects
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Specific to our non-deploy-preview deploys
|
||||||
|
# Confgure redirects using netlify.toml
|
||||||
|
|
||||||
|
# PWA Redirect
|
||||||
|
/* /index.html 200
|
||||||
15
.netlify/package.json
Normal file
15
.netlify/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "root",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.16.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "netlify deploy --prod --dir ./../platform/app/dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"netlify-cli": "^2.21.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
.netlify/www/_redirects
Normal file
10
.netlify/www/_redirects
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Specific to our deploy-preview
|
||||||
|
# Our docs are published using CircleCI + GitBook
|
||||||
|
# Confgure redirects using netlify.toml
|
||||||
|
|
||||||
|
# PWA Demo
|
||||||
|
/pwa/* /pwa/index.html 200
|
||||||
|
# UI Demo
|
||||||
|
/ui/* /ui/index.html 200
|
||||||
|
# UI Demo
|
||||||
|
/docs/* /docs/index.html 200
|
||||||
21
.netlify/www/index.html
Normal file
21
.netlify/www/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OHIF Viewer: Deploy Preview</title>
|
||||||
|
</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>
|
||||||
|
</html>
|
||||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
18.16.1
|
||||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.md
|
||||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"],
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"singleAttributePerLine": true,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
14
.scripts/dev.sh
Executable file
14
.scripts/dev.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# https://github.com/shelljs/shelljs
|
||||||
|
# https://github.com/shelljs/shelljs#exclude-options
|
||||||
|
PROJECT=$1
|
||||||
|
|
||||||
|
if [ -z "$PROJECT" ]
|
||||||
|
then
|
||||||
|
# Default
|
||||||
|
npx lerna run dev:viewer
|
||||||
|
else
|
||||||
|
eval "npx lerna run dev:$PROJECT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p 'Press [Enter] key to continue...'
|
||||||
273
.scripts/dicom-json-generator.js
Normal file
273
.scripts/dicom-json-generator.js
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
* This script uses nodejs to generate a JSON file from a DICOM study folder.
|
||||||
|
* You need to have dcmjs installed in your project.
|
||||||
|
* The JSON file can be used to load the study into the OHIF Viewer. You can get more detail
|
||||||
|
* in the DICOM JSON Data source on docs.ohif.org
|
||||||
|
*
|
||||||
|
* Usage: node dicom-json-generator.js <studyFolder> <urlPrefix> <outputJSONPath> <optional scheme>
|
||||||
|
*
|
||||||
|
* params:
|
||||||
|
* - studyFolder: path to the study folder which contains the DICOM files
|
||||||
|
* - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance
|
||||||
|
* we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the
|
||||||
|
* example since the data is hosted on S3 and each study is in a folder. So the url in the generated
|
||||||
|
* json file for the first instance of the first series of the first study will be
|
||||||
|
* dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1
|
||||||
|
*
|
||||||
|
* as you see the dicomweb is a prefix that is used to load the data into the viewer, which is suited when
|
||||||
|
* the .dcm file is hosted statically and can be accessed via a URL (like our example above)
|
||||||
|
* However, you can specify a new scheme bellow.
|
||||||
|
*
|
||||||
|
* - outputJSONPath: path to the output JSON file
|
||||||
|
* - scheme: default dicomweb if not provided
|
||||||
|
*/
|
||||||
|
const dcmjs = require('dcmjs');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const [studyDirectory, urlPrefix, outputPath, scheme = 'dicomweb'] = args;
|
||||||
|
|
||||||
|
if (args.length < 3 || args.length > 4) {
|
||||||
|
console.error(
|
||||||
|
'Usage: node dicom-json-generator.js <studyFolder> <urlPrefix> <outputJSONPath> [scheme]'
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = {
|
||||||
|
studies: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme) {
|
||||||
|
try {
|
||||||
|
const files = await recursiveReadDir(studyDirectory);
|
||||||
|
console.debug('Processing...');
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file.includes('.DS_Store') && !file.includes('.xml')) {
|
||||||
|
const arrayBuffer = await fs.readFile(file);
|
||||||
|
const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer);
|
||||||
|
const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
|
||||||
|
|
||||||
|
instance.fileLocation = createImageId(file, urlPrefix, studyDirectory, scheme);
|
||||||
|
processInstance(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Successfully loaded data');
|
||||||
|
|
||||||
|
model.studies.forEach(study => {
|
||||||
|
study.NumInstances = findInstancesNumber(study);
|
||||||
|
study.Modalities = findModalities(study).join('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.writeFile(outputPath, JSON.stringify(model, null, 2));
|
||||||
|
console.log('JSON saved');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recursiveReadDir(dir) {
|
||||||
|
let results = [];
|
||||||
|
const list = await fs.readdir(dir);
|
||||||
|
for (const file of list) {
|
||||||
|
const filePath = path.resolve(dir, file);
|
||||||
|
const stat = await fs.stat(filePath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const res = await recursiveReadDir(filePath);
|
||||||
|
results = results.concat(res);
|
||||||
|
} else {
|
||||||
|
results.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createImageId(fileLocation, urlPrefix, studyDirectory, scheme) {
|
||||||
|
const relativePath = path.relative(studyDirectory, fileLocation);
|
||||||
|
const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/');
|
||||||
|
return `${scheme}:${urlPrefix}${normalizedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processInstance(instance) {
|
||||||
|
const { StudyInstanceUID, SeriesInstanceUID } = instance;
|
||||||
|
let study = getStudy(StudyInstanceUID);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
study = createStudyMetadata(StudyInstanceUID, instance);
|
||||||
|
model.studies.push(study);
|
||||||
|
}
|
||||||
|
|
||||||
|
let series = getSeries(StudyInstanceUID, SeriesInstanceUID);
|
||||||
|
|
||||||
|
if (!series) {
|
||||||
|
series = createSeriesMetadata(instance);
|
||||||
|
study.series.push(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceMetaData =
|
||||||
|
instance.NumberOfFrames > 1
|
||||||
|
? createInstanceMetaDataMultiFrame(instance)
|
||||||
|
: createInstanceMetaData(instance);
|
||||||
|
|
||||||
|
series.instances.push(...[].concat(instanceMetaData));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStudy(StudyInstanceUID) {
|
||||||
|
return model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSeries(StudyInstanceUID, SeriesInstanceUID) {
|
||||||
|
const study = getStudy(StudyInstanceUID);
|
||||||
|
return study
|
||||||
|
? study.series.find(series => series.SeriesInstanceUID === SeriesInstanceUID)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findInstancesNumber = study => {
|
||||||
|
let numInstances = 0;
|
||||||
|
study.series.forEach(aSeries => {
|
||||||
|
numInstances = numInstances + aSeries.instances.length;
|
||||||
|
});
|
||||||
|
return numInstances;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findModalities = study => {
|
||||||
|
let modalities = new Set();
|
||||||
|
study.series.forEach(aSeries => {
|
||||||
|
modalities.add(aSeries.Modality);
|
||||||
|
});
|
||||||
|
return Array.from(modalities);
|
||||||
|
};
|
||||||
|
|
||||||
|
function createStudyMetadata(StudyInstanceUID, instance) {
|
||||||
|
return {
|
||||||
|
StudyInstanceUID,
|
||||||
|
StudyDescription: instance.StudyDescription,
|
||||||
|
StudyDate: instance.StudyDate,
|
||||||
|
StudyTime: instance.StudyTime,
|
||||||
|
PatientName: instance.PatientName,
|
||||||
|
PatientID: instance.PatientID || '1234', // this is critical to have
|
||||||
|
AccessionNumber: instance.AccessionNumber,
|
||||||
|
PatientAge: instance.PatientAge,
|
||||||
|
PatientSex: instance.PatientSex,
|
||||||
|
PatientWeight: instance.PatientWeight,
|
||||||
|
series: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function createSeriesMetadata(instance) {
|
||||||
|
return {
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
SeriesDescription: instance.SeriesDescription,
|
||||||
|
SeriesNumber: instance.SeriesNumber,
|
||||||
|
SeriesTime: instance.SeriesTime,
|
||||||
|
Modality: instance.Modality,
|
||||||
|
SliceThickness: instance.SliceThickness,
|
||||||
|
instances: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function commonMetaData(instance) {
|
||||||
|
return {
|
||||||
|
Columns: instance.Columns,
|
||||||
|
Rows: instance.Rows,
|
||||||
|
InstanceNumber: instance.InstanceNumber,
|
||||||
|
SOPClassUID: instance.SOPClassUID,
|
||||||
|
AcquisitionNumber: instance.AcquisitionNumber,
|
||||||
|
PhotometricInterpretation: instance.PhotometricInterpretation,
|
||||||
|
BitsAllocated: instance.BitsAllocated,
|
||||||
|
BitsStored: instance.BitsStored,
|
||||||
|
PixelRepresentation: instance.PixelRepresentation,
|
||||||
|
SamplesPerPixel: instance.SamplesPerPixel,
|
||||||
|
PixelSpacing: instance.PixelSpacing,
|
||||||
|
HighBit: instance.HighBit,
|
||||||
|
ImageOrientationPatient: instance.ImageOrientationPatient,
|
||||||
|
ImagePositionPatient: instance.ImagePositionPatient,
|
||||||
|
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||||
|
ImageType: instance.ImageType,
|
||||||
|
Modality: instance.Modality,
|
||||||
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
WindowCenter: instance.WindowCenter,
|
||||||
|
WindowWidth: instance.WindowWidth,
|
||||||
|
RescaleIntercept: instance.RescaleIntercept,
|
||||||
|
RescaleSlope: instance.RescaleSlope,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function conditionalMetaData(instance) {
|
||||||
|
return {
|
||||||
|
...(instance.ConceptNameCodeSequence && {
|
||||||
|
ConceptNameCodeSequence: instance.ConceptNameCodeSequence,
|
||||||
|
}),
|
||||||
|
...(instance.SeriesDate && { SeriesDate: instance.SeriesDate }),
|
||||||
|
...(instance.ReferencedSeriesSequence && {
|
||||||
|
ReferencedSeriesSequence: instance.ReferencedSeriesSequence,
|
||||||
|
}),
|
||||||
|
...(instance.SharedFunctionalGroupsSequence && {
|
||||||
|
SharedFunctionalGroupsSequence: instance.SharedFunctionalGroupsSequence,
|
||||||
|
}),
|
||||||
|
...(instance.PerFrameFunctionalGroupsSequence && {
|
||||||
|
PerFrameFunctionalGroupsSequence: instance.PerFrameFunctionalGroupsSequence,
|
||||||
|
}),
|
||||||
|
...(instance.ContentSequence && { ContentSequence: instance.ContentSequence }),
|
||||||
|
...(instance.ContentTemplateSequence && {
|
||||||
|
ContentTemplateSequence: instance.ContentTemplateSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CurrentRequestedProcedureEvidenceSequence && {
|
||||||
|
CurrentRequestedProcedureEvidenceSequence: instance.CurrentRequestedProcedureEvidenceSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CodingSchemeIdentificationSequence && {
|
||||||
|
CodingSchemeIdentificationSequence: instance.CodingSchemeIdentificationSequence,
|
||||||
|
}),
|
||||||
|
...(instance.RadiopharmaceuticalInformationSequence && {
|
||||||
|
RadiopharmaceuticalInformationSequence: instance.RadiopharmaceuticalInformationSequence,
|
||||||
|
}),
|
||||||
|
...(instance.ROIContourSequence && {
|
||||||
|
ROIContourSequence: instance.ROIContourSequence,
|
||||||
|
}),
|
||||||
|
...(instance.StructureSetROISequence && {
|
||||||
|
StructureSetROISequence: instance.StructureSetROISequence,
|
||||||
|
}),
|
||||||
|
...(instance.ReferencedFrameOfReferenceSequence && {
|
||||||
|
ReferencedFrameOfReferenceSequence: instance.ReferencedFrameOfReferenceSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CorrectedImage && { CorrectedImage: instance.CorrectedImage }),
|
||||||
|
...(instance.Units && { Units: instance.Units }),
|
||||||
|
...(instance.DecayCorrection && { DecayCorrection: instance.DecayCorrection }),
|
||||||
|
...(instance.AcquisitionDate && { AcquisitionDate: instance.AcquisitionDate }),
|
||||||
|
...(instance.AcquisitionTime && { AcquisitionTime: instance.AcquisitionTime }),
|
||||||
|
...(instance.PatientWeight && { PatientWeight: instance.PatientWeight }),
|
||||||
|
...(instance.NumberOfFrames && { NumberOfFrames: instance.NumberOfFrames }),
|
||||||
|
...(instance.FrameTime && { FrameTime: instance.FrameTime }),
|
||||||
|
...(instance.EncapsulatedDocument && { EncapsulatedDocument: instance.EncapsulatedDocument }),
|
||||||
|
...(instance.SequenceOfUltrasoundRegions && {
|
||||||
|
SequenceOfUltrasoundRegions: instance.SequenceOfUltrasoundRegions,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstanceMetaData(instance) {
|
||||||
|
const metadata = {
|
||||||
|
...commonMetaData(instance),
|
||||||
|
...conditionalMetaData(instance),
|
||||||
|
};
|
||||||
|
return { metadata, url: instance.fileLocation };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstanceMetaDataMultiFrame(instance) {
|
||||||
|
const instances = [];
|
||||||
|
const commonData = commonMetaData(instance);
|
||||||
|
const conditionalData = conditionalMetaData(instance);
|
||||||
|
|
||||||
|
for (let i = 1; i <= instance.NumberOfFrames; i++) {
|
||||||
|
const metadata = { ...commonData, ...conditionalData };
|
||||||
|
const result = { metadata, url: instance.fileLocation + `?frame=${i}` };
|
||||||
|
instances.push(result);
|
||||||
|
}
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme);
|
||||||
13
.vscode/extensions.json
vendored
Normal file
13
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"streetsidesoftware.code-spell-checker",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"mikestead.dotenv",
|
||||||
|
"bungcip.better-toml",
|
||||||
|
"silvenon.mdx",
|
||||||
|
"gruntfuggly.todo-tree",
|
||||||
|
"wayou.vscode-todo-highlight",
|
||||||
|
"bradlc.vscode-tailwindcss"
|
||||||
|
]
|
||||||
|
}
|
||||||
28
.vscode/launch.json
vendored
Normal file
28
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// "name": "Debug Jest Tests",
|
||||||
|
// "type": "node",
|
||||||
|
// "request": "launch",
|
||||||
|
// "runtimeArgs": [
|
||||||
|
// "--inspect-brk",
|
||||||
|
// "${workspaceRoot}/node_modules/.bin/jest",
|
||||||
|
// "--runInBand"
|
||||||
|
// ],
|
||||||
|
// "console": "integratedTerminal",
|
||||||
|
// "internalConsoleOptions": "neverOpen",
|
||||||
|
// "port": 9229
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
114
.vscode/settings.json
vendored
Normal file
114
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.rulers": [80, 120],
|
||||||
|
// ===
|
||||||
|
// Spacing
|
||||||
|
// ===
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.trimAutoWhitespace": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimFinalNewlines": true,
|
||||||
|
// ===
|
||||||
|
// Event Triggers
|
||||||
|
// ===
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"eslint.run": "onSave",
|
||||||
|
"jest.autoRun": "off",
|
||||||
|
"prettier.disableLanguages": ["html"],
|
||||||
|
"prettier.endOfLine": "lf",
|
||||||
|
"workbench.colorCustomizations": {},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
},
|
||||||
|
"cSpell.userWords": [
|
||||||
|
"aabb",
|
||||||
|
"architectured",
|
||||||
|
"attrname",
|
||||||
|
"Barksy",
|
||||||
|
"browserslist",
|
||||||
|
"bulkdata",
|
||||||
|
"Cacheable",
|
||||||
|
"cfun",
|
||||||
|
"clonedeep",
|
||||||
|
"Colormap",
|
||||||
|
"Colormaps",
|
||||||
|
"Comlink",
|
||||||
|
"cornerstonejs",
|
||||||
|
"Crosshairs",
|
||||||
|
"datasource",
|
||||||
|
"dcmjs",
|
||||||
|
"decache",
|
||||||
|
"decached",
|
||||||
|
"decaching",
|
||||||
|
"deepmerge",
|
||||||
|
"Dicom",
|
||||||
|
"dicomweb",
|
||||||
|
"DISPLAYSETS",
|
||||||
|
"glwindow",
|
||||||
|
"grababble",
|
||||||
|
"grabbable",
|
||||||
|
"Hounsfield",
|
||||||
|
"Interactable",
|
||||||
|
"Interactor",
|
||||||
|
"istyle",
|
||||||
|
"kitware",
|
||||||
|
"labelmap",
|
||||||
|
"labelmaps",
|
||||||
|
"livewire",
|
||||||
|
"Mergeable",
|
||||||
|
"multiframe",
|
||||||
|
"nifti",
|
||||||
|
"ofun",
|
||||||
|
"OHIF",
|
||||||
|
"polylines",
|
||||||
|
"POLYSEG",
|
||||||
|
"prapogation",
|
||||||
|
"precisionmetrics",
|
||||||
|
"prefetch",
|
||||||
|
"Prescaled",
|
||||||
|
"pydicom",
|
||||||
|
"Radiopharmaceutical",
|
||||||
|
"rasterizing",
|
||||||
|
"reconstructable",
|
||||||
|
"Rehydratable",
|
||||||
|
"renderable",
|
||||||
|
"resampler",
|
||||||
|
"resemblejs",
|
||||||
|
"reslice",
|
||||||
|
"resliced",
|
||||||
|
"Reslices",
|
||||||
|
"roadmap",
|
||||||
|
"ROADMAPS",
|
||||||
|
"Segmentations",
|
||||||
|
"semibold",
|
||||||
|
"sitk",
|
||||||
|
"SUBRESOLUTION",
|
||||||
|
"suvbsa",
|
||||||
|
"suvbw",
|
||||||
|
"suvlbm",
|
||||||
|
"textbox",
|
||||||
|
"thresholded",
|
||||||
|
"thresholding",
|
||||||
|
"timepoint",
|
||||||
|
"timepoints",
|
||||||
|
"TMTV",
|
||||||
|
"TOOLGROUP",
|
||||||
|
"tqdm",
|
||||||
|
"transferables",
|
||||||
|
"typedoc",
|
||||||
|
"unsubscriptions",
|
||||||
|
"uuidv",
|
||||||
|
"viewplane",
|
||||||
|
"viewports",
|
||||||
|
"Voxel",
|
||||||
|
"Voxels",
|
||||||
|
"Vtkjs",
|
||||||
|
"wado",
|
||||||
|
"wadors",
|
||||||
|
"wadouri",
|
||||||
|
"workerpool"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
.webpack/helpers/excludeNodeModulesExcept.js
Normal file
22
.webpack/helpers/excludeNodeModulesExcept.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function excludeNodeModulesExcept(modules) {
|
||||||
|
var pathSep = path.sep;
|
||||||
|
if (pathSep == '\\')
|
||||||
|
// must be quoted for use in a regexp:
|
||||||
|
pathSep = '\\\\';
|
||||||
|
var moduleRegExps = modules.map(function (modName) {
|
||||||
|
return new RegExp('node_modules' + pathSep + modName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return function (modulePath) {
|
||||||
|
if (/node_modules/.test(modulePath)) {
|
||||||
|
for (var i = 0; i < moduleRegExps.length; i++)
|
||||||
|
if (moduleRegExps[i].test(modulePath)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = excludeNodeModulesExcept;
|
||||||
29
.webpack/rules/cssToJavaScript.js
Normal file
29
.webpack/rules/cssToJavaScript.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
const path = require('path');
|
||||||
|
const tailwindcss = require('tailwindcss');
|
||||||
|
const tailwindConfigPath = path.resolve('../../platform/app/tailwind.config.js');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const devMode = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
const cssToJavaScript = {
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
//'style-loader',
|
||||||
|
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
|
||||||
|
{ loader: 'css-loader', options: { importLoaders: 1 } },
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
postcssOptions: {
|
||||||
|
verbose: true,
|
||||||
|
plugins: [
|
||||||
|
[tailwindcss(tailwindConfigPath)],
|
||||||
|
[autoprefixer('last 2 version', 'ie >= 11')],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = cssToJavaScript;
|
||||||
10
.webpack/rules/loadShaders.js
Normal file
10
.webpack/rules/loadShaders.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* This is exclusively used by `vtk.js` to bundle glsl files.
|
||||||
|
*/
|
||||||
|
const loadShaders = {
|
||||||
|
test: /\.glsl$/i,
|
||||||
|
include: /vtk\.js[\/\\]Sources/,
|
||||||
|
loader: 'shader-loader',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = loadShaders;
|
||||||
17
.webpack/rules/loadWebWorkers.js
Normal file
17
.webpack/rules/loadWebWorkers.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* This allows us to include web workers in our bundle, and VTK.js
|
||||||
|
* web workers in our bundle. While this increases bundle size, it
|
||||||
|
* cuts down on the number of includes we need for `script tag` usage.
|
||||||
|
*/
|
||||||
|
const loadWebWorkers = {
|
||||||
|
test: /\.worker\.js$/,
|
||||||
|
include: /vtk\.js[\/\\]Sources/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'worker-loader',
|
||||||
|
options: { inline: true, fallback: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = loadWebWorkers;
|
||||||
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;
|
||||||
45
.webpack/rules/transpileJavaScript.js
Normal file
45
.webpack/rules/transpileJavaScript.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const excludeNodeModulesExcept = require('./../helpers/excludeNodeModulesExcept.js');
|
||||||
|
|
||||||
|
function transpileJavaScript(mode) {
|
||||||
|
const exclude =
|
||||||
|
mode === 'production'
|
||||||
|
? excludeNodeModulesExcept([
|
||||||
|
// 'dicomweb-client',
|
||||||
|
// https://github.com/react-dnd/react-dnd/blob/master/babel.config.js
|
||||||
|
'react-dnd',
|
||||||
|
// https://github.com/dcmjs-org/dcmjs/blob/master/.babelrc
|
||||||
|
// https://github.com/react-dnd/react-dnd/issues/1342
|
||||||
|
// 'dcmjs', // contains: loglevelnext
|
||||||
|
// https://github.com/shellscape/loglevelnext#browser-support
|
||||||
|
// 'loglevelnext',
|
||||||
|
// https://github.com/dcmjs-org/dicom-microscopy-viewer/issues/35
|
||||||
|
// 'dicom-microscopy-viewer',
|
||||||
|
// https://github.com/openlayers/openlayers#supported-browsers
|
||||||
|
// 'ol', --> Should be fine
|
||||||
|
])
|
||||||
|
: excludeNodeModulesExcept([]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Include mjs, ts, tsx, js, and jsx files.
|
||||||
|
test: /\.(mjs|ts|js)x?$/,
|
||||||
|
// These are packages that are not transpiled to our lowest supported
|
||||||
|
// JS version (currently ES5). Most of these leverage ES6+ features,
|
||||||
|
// that we need to transpile to a different syntax.
|
||||||
|
exclude: [/(codecs)/, /(dicomicc)/, exclude],
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
// Find babel.config.js in monorepo root
|
||||||
|
// https://babeljs.io/docs/en/options#rootmode
|
||||||
|
rootMode: 'upward',
|
||||||
|
envName: mode,
|
||||||
|
cacheCompression: false,
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = transpileJavaScript;
|
||||||
236
.webpack/webpack.base.js
Normal file
236
.webpack/webpack.base.js
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
// ~~ ENV
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
//
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
// ~~ PLUGINS
|
||||||
|
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
|
// ~~ PackageJSON
|
||||||
|
// const vtkRules = require('vtk.js/Utilities/config/dependency.js').webpack.core
|
||||||
|
// .rules;
|
||||||
|
// ~~ RULES
|
||||||
|
// const loadShadersRule = require('./rules/loadShaders.js');
|
||||||
|
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||||
|
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
|
||||||
|
const cssToJavaScript = require('./rules/cssToJavaScript.js');
|
||||||
|
// Only uncomment for old v2 stylus
|
||||||
|
// const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
|
||||||
|
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||||
|
|
||||||
|
// ~~ ENV VARS
|
||||||
|
const NODE_ENV = process.env.NODE_ENV;
|
||||||
|
const QUICK_BUILD = process.env.QUICK_BUILD;
|
||||||
|
const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0';
|
||||||
|
|
||||||
|
// read from ../version.txt
|
||||||
|
const VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || '';
|
||||||
|
|
||||||
|
const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || '';
|
||||||
|
|
||||||
|
//
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const defineValues = {
|
||||||
|
/* Application */
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||||
|
'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG),
|
||||||
|
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||||
|
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||||
|
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||||
|
'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER),
|
||||||
|
'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH),
|
||||||
|
/* i18n */
|
||||||
|
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||||
|
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
||||||
|
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
||||||
|
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only redefine updated values. This avoids warning messages in the logs
|
||||||
|
if (!process.env.APP_CONFIG) {
|
||||||
|
defineValues['process.env.APP_CONFIG'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||||
|
if (!process.env.NODE_ENV) {
|
||||||
|
throw new Error('process.env.NODE_ENV not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = NODE_ENV === 'production' ? 'production' : 'development';
|
||||||
|
const isProdBuild = NODE_ENV === 'production';
|
||||||
|
const isQuickBuild = QUICK_BUILD === 'true';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
mode: isProdBuild ? 'production' : 'development',
|
||||||
|
devtool: isProdBuild ? 'source-map' : 'cheap-module-source-map',
|
||||||
|
entry: ENTRY,
|
||||||
|
optimization: {
|
||||||
|
// splitChunks: {
|
||||||
|
// // include all types of chunks
|
||||||
|
// chunks: 'all',
|
||||||
|
// },
|
||||||
|
//runtimeChunk: 'single',
|
||||||
|
minimize: isProdBuild,
|
||||||
|
sideEffects: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
// clean: true,
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
context: SRC_DIR,
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: true,
|
||||||
|
timings: true,
|
||||||
|
assets: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
modules: false,
|
||||||
|
children: false,
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
type: 'filesystem',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
noParse: [/(dicomicc)/],
|
||||||
|
rules: [
|
||||||
|
...(isProdBuild
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
test: /\.[jt]sx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: ['react-refresh/babel'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
test: /\.svg?$/,
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: '@svgr/webpack',
|
||||||
|
options: {
|
||||||
|
svgoConfig: {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'preset-default',
|
||||||
|
params: {
|
||||||
|
overrides: {
|
||||||
|
removeViewBox: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
prettier: false,
|
||||||
|
svgo: true,
|
||||||
|
titleProp: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
issuer: {
|
||||||
|
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transpileJavaScriptRule(mode),
|
||||||
|
loadWebWorkersRule,
|
||||||
|
// loadShadersRule,
|
||||||
|
{
|
||||||
|
test: /\.m?js/,
|
||||||
|
resolve: {
|
||||||
|
fullySpecified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cssToJavaScript,
|
||||||
|
// Note: Only uncomment the following if you are using the old style of stylus in v2
|
||||||
|
// Also you need to uncomment this platform/app/.webpack/rules/extractStyleChunks.js
|
||||||
|
// stylusToJavaScript,
|
||||||
|
{
|
||||||
|
test: /\.wasm/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg)$/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: 'assets/images/[name].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
], //.concat(vtkRules),
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
mainFields: ['module', 'browser', 'main'],
|
||||||
|
alias: {
|
||||||
|
// Viewer project
|
||||||
|
'@': path.resolve(__dirname, '../platform/app/src'),
|
||||||
|
'@components': path.resolve(__dirname, '../platform/app/src/components'),
|
||||||
|
'@hooks': path.resolve(__dirname, '../platform/app/src/hooks'),
|
||||||
|
'@routes': path.resolve(__dirname, '../platform/app/src/routes'),
|
||||||
|
'@state': path.resolve(__dirname, '../platform/app/src/state'),
|
||||||
|
'dicom-microscopy-viewer':
|
||||||
|
'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
|
||||||
|
},
|
||||||
|
// Which directories to search when resolving modules
|
||||||
|
modules: [
|
||||||
|
// Modules specific to this package
|
||||||
|
path.resolve(__dirname, '../node_modules'),
|
||||||
|
// Hoisted Yarn Workspace Modules
|
||||||
|
path.resolve(__dirname, '../../../node_modules'),
|
||||||
|
path.resolve(__dirname, '../platform/app/node_modules'),
|
||||||
|
path.resolve(__dirname, '../platform/ui/node_modules'),
|
||||||
|
SRC_DIR,
|
||||||
|
],
|
||||||
|
// Attempt to resolve these extensions in order.
|
||||||
|
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'],
|
||||||
|
// symlinked resources are resolved to their real path, not their symlinked location
|
||||||
|
symlinks: true,
|
||||||
|
fallback: {
|
||||||
|
fs: false,
|
||||||
|
path: false,
|
||||||
|
zlib: false,
|
||||||
|
buffer: require.resolve('buffer'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin(defineValues),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
Buffer: ['buffer', 'Buffer'],
|
||||||
|
}),
|
||||||
|
...(isProdBuild ? [] : [new ReactRefreshWebpackPlugin({ overlay: false })]),
|
||||||
|
// Uncomment to generate bundle analyzer
|
||||||
|
// new BundleAnalyzerPlugin(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isProdBuild) {
|
||||||
|
config.optimization.minimizer = [
|
||||||
|
new TerserJSPlugin({
|
||||||
|
parallel: true,
|
||||||
|
terserOptions: {},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isQuickBuild) {
|
||||||
|
config.optimization.minimize = false;
|
||||||
|
config.devtool = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
2772
CHANGELOG.md
Normal file
2772
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at danny.ri.brown+OHIFcoc@gmail.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
||||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
See our contributing guidelines at [`https://docs.ohif.org`](https://docs.ohif.org/development/contributing.html)
|
||||||
297
DATACITATION.md
Normal file
297
DATACITATION.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# OHIF public demo data sets
|
||||||
|
|
||||||
|
The OHIF Viewer's public demo page, available at https://viewer.ohif.org/, uses publicly anonymized demo datasets.
|
||||||
|
These datasets were mostly obtained from the [NIH NCI Imaging Data Commons](https://datacommons.cancer.gov/repository/imaging-data-commons)
|
||||||
|
and [NIH NCI TCIA](https://www.cancerimagingarchive.net/). Before listing the datasets,
|
||||||
|
we would like to extend a special thank you to all groups who have made their datasets publicly available.
|
||||||
|
Without them, we would not have been able to create this demo page.
|
||||||
|
|
||||||
|
Please find below the list of datasets used on the demo page, along with their respective citations.
|
||||||
|
|
||||||
|
|
||||||
|
## Platforms
|
||||||
|
|
||||||
|
### NIH NCI IDC
|
||||||
|
|
||||||
|
- Fedorov, A., Longabaugh, W.J., Pot, D., Clunie, D.A., Pieper, S., Aerts, H.J., Homeyer, A., Lewis, R., Akbarzadeh, A., Bontempi, D. and Clifford, W., 2021. NCI imaging data commons. Cancer research, 81(16), p.4188.
|
||||||
|
|
||||||
|
### NIH NCI TCIA
|
||||||
|
|
||||||
|
- Clark, K., Vendt, B., Smith, K., Freymann, J., Kirby, J., Koppel, P., Moore, S., Phillips, S., Maffitt, D., Pringle, M., Tarbox, L., & Prior, F. (2013). The Cancer Imaging Archive (TCIA): Maintaining and Operating a Public Information Repository. Journal of Digital Imaging, 26(6), 1045–1057. https://doi.org/10.1007/s10278-013-9622-7
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Datasets
|
||||||
|
Below you can find the StudyInstanceUID of the studies that are used in the demo page along with their citations.
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.267424821384663813780850856506829388886
|
||||||
|
|
||||||
|
Segmentation of Vestibular Schwannoma from Magnetic Resonance Imaging: An Open Annotated Dataset and Baseline Algorithm (Vestibular-Schwannoma-SEG)
|
||||||
|
|
||||||
|
- Shapey, J., Kujawa, A., Dorent, R., Wang, G., Bisdas, S., Dimitriadis, A., Grishchuck, D., Paddick, I., Kitchen, N., Bradford, R., Saeed, S., Ourselin, S., & Vercauteren, T. (2021). Segmentation of Vestibular Schwannoma from Magnetic Resonance Imaging: An Open Annotated Dataset and Baseline Algorithm [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.9YTJ-5Q73
|
||||||
|
|
||||||
|
- Shapey, J., Kujawa, A., Dorent, R., Wang, G., Dimitriadis, A., Grishchuk, D., Paddick, I., Kitchen, N., Bradford, R., Saeed, S. R., Bisdas, S., Ourselin, S., & Vercauteren, T. (2021). Segmentation of vestibular schwannoma from MRI, an open annotated dataset and baseline algorithm. In Scientific Data (Vol. 8, Issue 1). Springer Science and Business Media LLC. https://doi.org/10.1038/s41597-021-01064-w
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.7009.2403.871108593056125491804754960339
|
||||||
|
|
||||||
|
|
||||||
|
ACRIN-NSCLC-FDG-PET (ACRIN 6668)
|
||||||
|
|
||||||
|
- Kinahan, P., Muzi, M., Bialecki, B., Herman, B., & Coombs, L. (2019). Data from the ACRIN 6668 Trial NSCLC-FDG-PET (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/tcia.2019.30ilqfcl
|
||||||
|
|
||||||
|
- Machtay, M., Duan, F., Siegel, B. A., Snyder, B. S., Gorelick, J. J., Reddin, J. S., Munden, R., Johnson, D. W., Wilf, L. H., DeNittis, A., Sherwin, N., Cho, K. H., Kim, S., Videtic, G., Neumann, D. R., Komaki, R., Macapinlac, H., Bradley, J. D., & Alavi, A. (2013). Prediction of Survival by [18F]Fluorodeoxyglucose Positron Emission Tomography in Patients With Locally Advanced Non–Small-Cell Lung Cancer Undergoing Definitive Chemoradiation Therapy: Results of the ACRIN 6668/RTOG 0235 Trial. In Journal of Clinical Oncology (Vol. 31, Issue 30, pp. 3823–3830). American Society of Clinical Oncology (ASCO). https://doi.org/10.1200/jco.2012.47.5947
|
||||||
|
|
||||||
|
|
||||||
|
### 2.25.103659964951665749659160840573802789777
|
||||||
|
|
||||||
|
The Cancer Genome Atlas Glioblastoma Multiforme Collection (TCGA-GBM)
|
||||||
|
|
||||||
|
- Scarpace, L., Mikkelsen, T., Cha, S., Rao, S., Tekchandani, S., Gutman, D., Saltz, J. H., Erickson, B. J., Pedano, N., Flanders, A. E., Barnholtz-Sloan, J., Ostrom, Q., Barboriak, D., & Pierce, L. J. (2016). The Cancer Genome Atlas Glioblastoma Multiforme Collection (TCGA-GBM) (Version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.RNYFUYE9
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.256467663913010332776401703474716742458
|
||||||
|
|
||||||
|
Abdominal or pelvic enhanced CT images within 10 days before surgery of 230 patients with stage II colorectal cancer (StageII-Colorectal-CT)
|
||||||
|
|
||||||
|
|
||||||
|
- Tong T., Li M. (2022) Abdominal or pelvic enhanced CT images within 10 days before surgery of 230 patients with stage II colorectal cancer (StageII-Colorectal-CT) [Dataset]. The Cancer Imaging Archive. DOI: https://doi.org/10.7937/p5k5-tg43
|
||||||
|
|
||||||
|
- Li, M., Gong, J., Bao, Y., Huang, D., Peng, J., & Tong, T. (2022). Special issue “The advance of solid tumor research in China”: Prognosis prediction for stage II colorectal cancer by fusing computed tomography radiomics and deep‐learning features of primary lesions and peripheral lymph nodes. In International Journal of Cancer. Wiley. https://doi.org/10.1002/ijc.34053
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.3023.4024.215308722288168917637555384485
|
||||||
|
|
||||||
|
The Cancer Genome Atlas Sarcoma Collection (TCGA-SARC)
|
||||||
|
|
||||||
|
- Roche, C., Bonaccio, E., & Filippini, J. (2016). The Cancer Genome Atlas Sarcoma Collection (TCGA-SARC) (Version 3) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.CX6YLSUX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.105216574054253895819671475627
|
||||||
|
|
||||||
|
BREAST-DIAGNOSIS
|
||||||
|
|
||||||
|
|
||||||
|
- Bloch, B. Nicolas, Jain, Ashali, & Jaffe, C. Carl. (2015). BREAST-DIAGNOSIS [Data set]. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2015.SDNRQXXR
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.1706.8374.643249677828306008300337414785
|
||||||
|
|
||||||
|
Multimodality annotated HCC cases with and without advanced imaging segmentation (HCC-TACE-Seg)
|
||||||
|
|
||||||
|
|
||||||
|
- Moawad, A. W., Fuentes, D., Morshid, A., Khalaf, A. M., Elmohr, M. M., Abusaif, A., Hazle, J. D., Kaseb, A. O., Hassan, M., Mahvash, A., Szklaruk, J., Qayyom, A., & Elsayes, K. (2021). Multimodality annotated HCC cases with and without advanced imaging segmentation [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.5FNA-0924
|
||||||
|
|
||||||
|
- Morshid, A., Elsayes, K. M., Khalaf, A. M., Elmohr, M. M., Yu, J., Kaseb, A. O., Hassan, M., Mahvash, A., Wang, Z., Hazle, J. D., & Fuentes, D. (2019). A Machine Learning Model to Predict Hepatocellular Carcinoma Response to Transcatheter Arterial Chemoembolization. Radiology: Artificial Intelligence, 1(5), e180021. https://doi.org/10.1148/ryai.2019180021
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.1188.2803.137585363493444318569098508293
|
||||||
|
|
||||||
|
Ultrasound data of a variety of liver masses (B-mode-and-CEUS-Liver)
|
||||||
|
|
||||||
|
- Eisenbrey, J., Lyshchik, A., & Wessner, C. (2021). Ultrasound data of a variety of liver masses [Data set]. The Cancer Imaging Archive. DOI: https://doi.org/10.7937/TCIA.2021.v4z7-tc39
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.32722.99.99.62087908186665265759322018723889952421
|
||||||
|
|
||||||
|
NSCLC-Radiomics
|
||||||
|
|
||||||
|
- Aerts, H. J. W. L., Wee, L., Rios Velazquez, E., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2019). Data From NSCLC-Radiomics (version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI
|
||||||
|
|
||||||
|
|
||||||
|
- Aerts, H. J. W. L., Velazquez, E. R., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2014, June 3). Decoding tumour phenotype by noninvasive imaging using a quantitative radiomics approach. Nature Communications. Nature Publishing Group. https://doi.org/10.1038/ncomms5006 (link)
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.3671.4754.298665348758363466150039312520
|
||||||
|
|
||||||
|
QIN-PROSTATE-Repeatability
|
||||||
|
|
||||||
|
- Fedorov, A; Schwier, M; Clunie, D; Herz, C; Pieper, S; Kikinis, R; Tempany, C; Fennessy, F. (2018). Data From QIN-PROSTATE-Repeatability. The Cancer Imaging Archive. DOI: 10.7937/K9/TCIA.2018.MR1CKGND
|
||||||
|
|
||||||
|
|
||||||
|
- Fedorov A, Vangel MG, Tempany CM, Fennessy FM. Multiparametric Magnetic Resonance Imaging of the Prostate: Repeatability of Volume and Apparent Diffusion Coefficient Quantification. Investigative Radiology. 52, 538–546 (2017). DOI: 10.1097/RLI.0000000000000382
|
||||||
|
|
||||||
|
- Fedorov, A., Schwier, M., Clunie, D., Herz, C., Pieper, S., Kikinis,R., Tempany, C. & Fennessy, F. An annotated test-retest collection of prostate multiparametric MRI. Scientific Data 5, 180281 (2018). DOI:
|
||||||
|
|
||||||
|
### 2.25.141277760791347900862109212450152067508
|
||||||
|
|
||||||
|
The Clinical Proteomic Tumor Analysis Consortium Clear Cell Renal Cell Carcinoma Collection (CPTAC-CCRCC)
|
||||||
|
|
||||||
|
- National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC). (2018). The Clinical Proteomic Tumor Analysis Consortium Clear Cell Renal Cell Carcinoma Collection (CPTAC-CCRCC) (Version 10) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2018.OBLAMN27
|
||||||
|
|
||||||
|
- The CPTAC program requests that publications using data from this program include the following statement: “Data used in this publication were generated by the National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC).”
|
||||||
|
|
||||||
|
|
||||||
|
### 2.25.275741864483510678566144889372061815320
|
||||||
|
|
||||||
|
National Lung Screening Trial
|
||||||
|
|
||||||
|
- National Lung Screening Trial Research Team. (2013). Data from the National Lung Screening Trial (NLST) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.HMQ8-J677
|
||||||
|
|
||||||
|
- National Lung Screening Trial Research Team*; Aberle DR, Adams AM, Berg CD, Black WC, Clapp JD, Fagerstrom RM, Gareen IF, Gatsonis C, Marcus PM, Sicks JD (2011). Reduced Lung-Cancer Mortality with Low-Dose Computed Tomographic Screening. New England Journal of Medicine, 365(5), 395–409. https://doi.org/10.1056/nejmoa1102873
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.99.1071.26968527900428638961173806140069
|
||||||
|
|
||||||
|
Stony Brook University COVID-19 Positive Cases (COVID-19-NY-SBU)
|
||||||
|
|
||||||
|
- Saltz, J., Saltz, M., Prasanna, P., Moffitt, R., Hajagos, J., Bremer, E., Balsamo, J., & Kurc, T. (2021). Stony Brook University COVID-19 Positive Cases [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.BBAG-2923
|
||||||
|
|
||||||
|
|
||||||
|
### 2.16.840.1.114362.1.11972228.22789312658.616067305.306.2
|
||||||
|
|
||||||
|
https://data.kitware.com/
|
||||||
|
|
||||||
|
|
||||||
|
### 1.2.276.0.7230010.3.1.2.296485376.1.1665793212.499772
|
||||||
|
### 2.25.269859997690759739055099378767846712697
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.5099.8010.217836670708542506360829799868
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.232252967813565730694525674696
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.105216574054253895819671475627
|
||||||
|
### 1.3.6.1.4.1.5962.99.1.1117.5035.1620319789811.1.2.1
|
||||||
|
### 1.3.6.1.4.1.5962.99.1.1123.9231.1620326176300.1.2.1
|
||||||
|
### 1.3.6.1.4.1.5962.99.1.1126.3483.1620329455972.1.2.1
|
||||||
|
|
||||||
|
https://github.com/ImagingInformatics/hackathon-images
|
||||||
|
|
||||||
|
### 2.16.124.113543.6004.101.103.20021117.162333.1
|
||||||
|
### 2.16.124.113543.6004.101.103.20021117.190619.1
|
||||||
|
### 2.16.124.113543.6004.101.103.20021117.123455.1
|
||||||
|
### 2.16.124.113543.6004.101.103.20021117.061159.1
|
||||||
|
|
||||||
|
https://www.aapm.org/
|
||||||
|
|
||||||
|
|
||||||
|
### 1.2.840.113619.2.30.1.1762295590.1623.978668949.886
|
||||||
|
|
||||||
|
|
||||||
|
### 1.2.276.0.7230010.3.1.2.447481088.1.1669202398.851612
|
||||||
|
|
||||||
|
Custom data SPECT, specifically I123-FP-CIT (DaTSCAN) SPECT, evaluates the dopaminergic system to diagnose Parkinson's disease, especially when tremor symptoms are unclear. It helps distinguish Parkinson's disease from treatment-related tremor.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.9328.50.1.54652
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/rider-pilot/
|
||||||
|
|
||||||
|
Lung Image Database Consortium (LIDC). (2023) RIDER Pilot [Data set]. The Cancer Imaging Archive (TCIA). https://doi.org/10.7937/m87f-mz83
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.331759366792756327296606233801322964986
|
||||||
|
|
||||||
|
Mayr, N., Yuh, W. T. C., Bowen, S., Harkenrider, M., Knopp, M. V., Lee, E. Y.-P., Leung, E., Lo, S. S., Small Jr., W., & Wolfson, A. H. (2023). Cervical Cancer – Tumor Heterogeneity: Serial Functional and Molecular Imaging Across the Radiation Therapy Course in Advanced Cervical Cancer (Version 1) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/ERZ5-QZ59
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/cc-tumor-heterogeneity/
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.297577087050970310787702792940607009472
|
||||||
|
|
||||||
|
Eslick, E. M., Kipritidis, J., Gradinscak, D., Stevens, M. J., Bailey, D. L., Harris, B., Booth, J. T., & Keall, P. J. (2022). CT Ventilation as a functional imaging modality for lung cancer radiotherapy (CT-vs-PET-Ventilation-Imaging) (Version 1) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/3ppx-7s22
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/ct-vs-pet-ventilation-imaging/
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.2103.7010.634114621738943599785009586807
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.2103.7010.135953723682765205394176991681
|
||||||
|
|
||||||
|
Huang, W., Tudorica, A., Chui, S., Kemmer, K., Naik, A., Troxell, M., Oh, K., Roy, N., Afzal, A., & Holtorf, M. (2014). Variations of dynamic contrast-enhanced magnetic resonance imaging in evaluation of breast cancer therapy response: a multicenter data analysis challenge (QIN Breast DCE-MRI) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/k9/tcia.2014.a2n1ixox
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/qin-breast-dce-mri/
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.1.24766180081901755714059656629507905556
|
||||||
|
|
||||||
|
|
||||||
|
Cancer Moonshot Biobank. (2023). Cancer Moonshoot Biobank – Acute Myeloid Leukemia (CMB-AML) (Version 4) [Dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/PCTE-6M66
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/cmb-aml/
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.3098.5025.285242291560760827564488897577
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/anti-pd-1_lung/
|
||||||
|
|
||||||
|
Madhavi, P., Patel, S., & Tsao, A. S. (2019). Data from Anti-PD-1 Immunotherapy Lung [Data set]. The Cancer Imaging Archive. DOI: 10.7937/tcia.2019.zjjwb9ip
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.1.84416332615988066829602832830236187384
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/cmb-pca/
|
||||||
|
|
||||||
|
Cancer Moonshot Biobank. (2022). Cancer Moonshot Biobank – Prostate Cancer Collection (CMB-PCA) (Version 7) [Dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/25T7-6Y12
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.32722.99.99.239341353911714368772597187099978969331
|
||||||
|
|
||||||
|
Aerts, H. J. W. L., Wee, L., Rios Velazquez, E., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2014). Data From NSCLC-Radiomics (version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/nsclc-radiomics/
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.7085.2626.494695569589117268722281491772
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/cptac-ucec/
|
||||||
|
|
||||||
|
|
||||||
|
National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC). (2019). The Clinical Proteomic Tumor Analysis Consortium Uterine Corpus Endometrial Carcinoma Collection (CPTAC-UCEC) (Version 12) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2018.3R3JUISW
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.207544490797667703011829289839681390478
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/remind/
|
||||||
|
|
||||||
|
Juvekar, P., Dorent, R., Kögl, F., Torio, E., Barr, C., Rigolo, L., Galvin, C., Jowkar, N., Kazi, A., Haouchine, N., Cheema, H., Navab, N., Pieper, S., Wells, W. M., Bi, W. L., Golby, A., Frisken, S., & Kapur, T. (2023). The Brain Resection Multimodal Imaging Database (ReMIND) (Version 1) [dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/3RAG-D070
|
||||||
|
|
||||||
|
### 1.3.12.2.1107.5.1.4.60175.30000008042114404745300000010
|
||||||
|
|
||||||
|
Gavrielides, M. A., Kinnard, L. M., Myers, K. J., Peregoy, J., Pritchard, W. F., Zeng, R., Esparza, J., Karanian, J., & Petrick, N. (2015). Data From Phantom FDA [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/k9/TCIA.2015.orbjkmux
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/phantom-fda/
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.6834.5010.992793141464713669479982159310
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/4d-lung/
|
||||||
|
|
||||||
|
|
||||||
|
Hugo, G. D., Weiss, E., Sleeman, W. C., Balik, S., Keall, P. J., Lu, J., & Williamson, J. F. (2016). Data from 4D Lung Imaging of NSCLC Patients (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.ELN8YGLE
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.9328.50.17.15423521354819720574322014551955370036
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/rider-lung-pet-ct/
|
||||||
|
|
||||||
|
Muzi P, Wanner M, & Kinahan P. (2015). Data From RIDER Lung PET-CT. The Cancer Imaging Archive. https://doi.org/10.7937/k9/tcia.2015.ofip7tvm
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.9823.1001.134394060407147891170882809392
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/prostate-mri/
|
||||||
|
|
||||||
|
Choyke P, Turkbey B, Pinto P, Merino M, Wood B. (2016). Data From PROSTATE-MRI. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2016.6046GUDv
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.191696062987463500085282581898315738844
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/upenn-gbm/
|
||||||
|
|
||||||
|
Bakas, S., Sako, C., Akbari, H., Bilello, M., Sotiras, A., Shukla, G., Rudie, J. D., Flores Santamaria, N., Fathi Kazerooni, A., Pati, S., Rathore, S., Mamourian, E., Ha, S. M., Parker, W., Doshi, J., Baid, U., Bergman, M., Binder, Z. A., Verma, R., … Davatzikos, C. (2021). Multi-parametric magnetic resonance imaging (mpMRI) scans for de novo Glioblastoma (GBM) patients from the University of Pennsylvania Health System (UPENN-GBM) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.709X-DN49
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.921758700577562664959693695481
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/breast-diagnosis/
|
||||||
|
|
||||||
|
Bloch, B. Nicolas, Jain, Ashali, & Jaffe, C. Carl. (2015). BREAST-DIAGNOSIS [Data set]. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2015.SDNRQXXR
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.6.1.4.1.14519.5.2.1.1620.1225.189514895974227080410265976065
|
||||||
|
|
||||||
|
Comstock, C. E., Gatsonis, C., Newstead, G. M., Snyder, B. S., Gareen, I. F., Bergin, J. T., Rahbar, H., Sung, J. S., Jacobs, C., Harvey, J. A., Nicholson, M. H., Ward, R. C., Holt, J., Prather, A., Miller, K. D., Schnall, M. D., & Kuhl, C. K. (2023). Abbreviated Breast MRI and Digital Tomosynthesis Mammography in Screening Women With Dense Breasts (EA1141) (Version 1) [dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/2BAS-HR33
|
||||||
|
|
||||||
|
https://www.cancerimagingarchive.net/collection/ea1141/
|
||||||
|
|
||||||
|
### 1.2.276.0.7230010.3.1.2.2155604110.4180.1021041295.21
|
||||||
|
|
||||||
|
From OFFIS DICOM-Team
|
||||||
|
|
||||||
|
https://www.offis.de/
|
||||||
|
OFFIS DICOM-Team
|
||||||
79
Dockerfile
Normal file
79
Dockerfile
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# This dockerfile is used to publish the `ohif/app` image on dockerhub.
|
||||||
|
#
|
||||||
|
# It's a good example of how to build our static application and package it
|
||||||
|
# with a web server capable of hosting it as static content.
|
||||||
|
#
|
||||||
|
# docker build
|
||||||
|
# --------------
|
||||||
|
# If you would like to use this dockerfile to build and tag an image, make sure
|
||||||
|
# you set the context to the project's root directory:
|
||||||
|
# https://docs.docker.com/engine/reference/commandline/build/
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# SUMMARY
|
||||||
|
# --------------
|
||||||
|
# This dockerfile has two stages:
|
||||||
|
#
|
||||||
|
# 1. Building the React application for production
|
||||||
|
# 2. Setting up our Nginx (Alpine Linux) image w/ step one's output
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# Stage 1: Build the application
|
||||||
|
# docker build -t ohif/viewer:latest .
|
||||||
|
FROM node:18.16.1-slim as json-copier
|
||||||
|
|
||||||
|
RUN mkdir /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY ["package.json", "yarn.lock", "preinstall.js", "./"]
|
||||||
|
COPY extensions /usr/src/app/extensions
|
||||||
|
COPY modes /usr/src/app/modes
|
||||||
|
COPY platform /usr/src/app/platform
|
||||||
|
|
||||||
|
# Find and remove non-package.json files
|
||||||
|
#RUN find extensions \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
|
||||||
|
#RUN find modes \! -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
|
||||||
|
FROM node:18.16.1-slim as builder
|
||||||
|
RUN apt-get update && apt-get install -y build-essential python3
|
||||||
|
RUN mkdir /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY --from=json-copier /usr/src/app .
|
||||||
|
|
||||||
|
# Run the install before copying the rest of the files
|
||||||
|
RUN yarn config set workspaces-experimental true
|
||||||
|
RUN yarn install --frozen-lockfile --verbose
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# To restore workspaces symlinks
|
||||||
|
RUN yarn install --frozen-lockfile --verbose
|
||||||
|
|
||||||
|
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||||
|
ENV QUICK_BUILD true
|
||||||
|
# ENV GENERATE_SOURCEMAP=false
|
||||||
|
# ENV REACT_APP_CONFIG=config/default.js
|
||||||
|
|
||||||
|
RUN yarn run build
|
||||||
|
|
||||||
|
# Stage 3: Bundle the built application into a Docker container
|
||||||
|
# which runs Nginx using Alpine Linux
|
||||||
|
FROM nginxinc/nginx-unprivileged:1.25-alpine as final
|
||||||
|
#RUN apk add --no-cache bash
|
||||||
|
ENV PORT=80
|
||||||
|
RUN rm /etc/nginx/conf.d/default.conf
|
||||||
|
USER nginx
|
||||||
|
COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src
|
||||||
|
RUN chmod 777 /usr/src/entrypoint.sh
|
||||||
|
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"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Open Health Imaging Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
222
README.md
Normal file
222
README.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<div align="center">
|
||||||
|
<h1>OHIF prod-ab branch</h1>
|
||||||
|
<p>Branch prod-ab adalah OHIF dari PT. SIM yang siap untuk digunakan versi production saat ini.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://docs.ohif.org/"><strong>Read The Docs</strong></a>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://viewer.ohif.org/">Live Demo</a> |
|
||||||
|
<a href="https://ui.ohif.org/">Component Library</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install)
|
||||||
|
- [Node 18+](https://nodejs.org/en/)
|
||||||
|
- Yarn Workspaces should be enabled on your machine:
|
||||||
|
- `yarn config set workspaces-experimental true`
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
- `git clone https://github.com/OHIF/Viewers.git`
|
||||||
|
2. Navigate to the cloned project's directory
|
||||||
|
3. `yarn install` to restore dependencies and link projects
|
||||||
|
4. `yarn dev` to start the development server
|
||||||
|
|
||||||
|
#### To Develop
|
||||||
|
|
||||||
|
_From this repository's root directory:_
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable Yarn Workspaces
|
||||||
|
yarn config set workspaces-experimental true
|
||||||
|
|
||||||
|
# Restore dependencies
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# Run with hot-reload
|
||||||
|
yarn run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
These commands are available from the root directory. Each project directory
|
||||||
|
also supports a number of commands that can be found in their respective
|
||||||
|
`README.md` and `package.json` files.
|
||||||
|
|
||||||
|
| Commands | Description |
|
||||||
|
| ---------------------------- | ------------------------------------------------------------- |
|
||||||
|
| **Develop** | |
|
||||||
|
| `dev` or `start` | Default development experience for Viewer |
|
||||||
|
| `test:unit` | Jest multi-project test runner; overall coverage |
|
||||||
|
| **Deploy** | |
|
||||||
|
| `build`\* | Builds production output for our PWA Viewer | |
|
||||||
|
|
||||||
|
\* - For more information on our different builds, check out our [Deploy
|
||||||
|
Docs][deployment-docs]
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
The OHIF Medical Image Viewing Platform is maintained as a
|
||||||
|
[`monorepo`][monorepo]. This means that this repository, instead of containing a
|
||||||
|
single project, contains many projects. If you explore our project structure,
|
||||||
|
you'll see the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.
|
||||||
|
├── extensions #
|
||||||
|
│ ├── _example # Skeleton of example extension
|
||||||
|
│ ├── default # basic set of useful functionalities (datasources, panels, etc)
|
||||||
|
│ ├── cornerstone # image rendering and tools w/ Cornerstone3D
|
||||||
|
│ ├── cornerstone-dicom-sr # DICOM Structured Report rendering and export
|
||||||
|
│ ├── cornerstone-dicom-sr # DICOM Structured Report rendering and export
|
||||||
|
│ ├── cornerstone-dicom-seg # DICOM Segmentation rendering and export
|
||||||
|
│ ├── cornerstone-dicom-rt # DICOM RTSTRUCT rendering
|
||||||
|
│ ├── cornerstone-microscopy # Whole Slide Microscopy rendering
|
||||||
|
│ ├── dicom-pdf # PDF rendering
|
||||||
|
│ ├── dicom-video # DICOM RESTful Services
|
||||||
|
│ ├── measurement-tracking # Longitudinal measurement tracking
|
||||||
|
│ ├── tmtv # Total Metabolic Tumor Volume (TMTV) calculation
|
||||||
|
|
|
||||||
|
|
||||||
|
│
|
||||||
|
├── modes #
|
||||||
|
│ ├── _example # Skeleton of example mode
|
||||||
|
│ ├── basic-dev-mode # Basic development mode
|
||||||
|
│ ├── longitudinal # Longitudinal mode (measurement tracking)
|
||||||
|
│ ├── tmtv # Total Metabolic Tumor Volume (TMTV) calculation mode
|
||||||
|
│ └── microscopy # Whole Slide Microscopy mode
|
||||||
|
│
|
||||||
|
├── platform #
|
||||||
|
│ ├── core # Business Logic
|
||||||
|
│ ├── i18n # Internationalization Support
|
||||||
|
│ ├── ui # React component library
|
||||||
|
│ ├── docs # Documentation
|
||||||
|
│ └── viewer # Connects platform and extension projects
|
||||||
|
│
|
||||||
|
├── ... # misc. shared configuration
|
||||||
|
├── lerna.json # MonoRepo (Lerna) settings
|
||||||
|
├── package.json # Shared devDependencies and commands
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
To acknowledge the OHIF Viewer in an academic publication, please cite
|
||||||
|
|
||||||
|
> _Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework
|
||||||
|
> for Building Web-Based Imaging Applications to Support Cancer Research_
|
||||||
|
>
|
||||||
|
> Erik Ziegler, Trinity Urban, Danny Brown, James Petts, Steve D. Pieper, Rob
|
||||||
|
> Lewis, Chris Hafey, and Gordon J. Harris
|
||||||
|
>
|
||||||
|
> _JCO Clinical Cancer Informatics_, no. 4 (2020), 336-345, DOI:
|
||||||
|
> [10.1200/CCI.19.00131](https://www.doi.org/10.1200/CCI.19.00131)
|
||||||
|
>
|
||||||
|
> Open-Access on Pubmed Central:
|
||||||
|
> https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/
|
||||||
|
|
||||||
|
or, for v1, please cite:
|
||||||
|
|
||||||
|
> _LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer
|
||||||
|
> Imaging Research and Clinical Trials_
|
||||||
|
>
|
||||||
|
> Trinity Urban, Erik Ziegler, Rob Lewis, Chris Hafey, Cheryl Sadow, Annick D.
|
||||||
|
> Van den Abbeele and Gordon J. Harris
|
||||||
|
>
|
||||||
|
> _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI:
|
||||||
|
> [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
|
||||||
|
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.
|
||||||
|
|
||||||
|
This work is supported primarily by the National Institutes of Health, National
|
||||||
|
Cancer Institute, Informatics Technology for Cancer Research (ITCR) program,
|
||||||
|
under a
|
||||||
|
[grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104).
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
MIT © [OHIF](https://github.com/OHIF)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Links
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- Badges -->
|
||||||
|
[lerna-image]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg
|
||||||
|
[lerna-url]: https://lerna.js.org/
|
||||||
|
[netlify-image]: https://api.netlify.com/api/v1/badges/32708787-c9b0-4634-b50f-7ca41952da77/deploy-status
|
||||||
|
[netlify-url]: https://app.netlify.com/sites/ohif-dev/deploys
|
||||||
|
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
|
||||||
|
[circleci-image]: https://circleci.com/gh/OHIF/Viewers.svg?style=svg
|
||||||
|
[circleci-url]: https://circleci.com/gh/OHIF/Viewers
|
||||||
|
[codecov-image]: https://codecov.io/gh/OHIF/Viewers/branch/master/graph/badge.svg
|
||||||
|
[codecov-url]: https://codecov.io/gh/OHIF/Viewers/branch/master
|
||||||
|
[prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square
|
||||||
|
[prettier-url]: https://github.com/prettier/prettier
|
||||||
|
[semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
||||||
|
[semantic-url]: https://github.com/semantic-release/semantic-release
|
||||||
|
<!-- ROW -->
|
||||||
|
[npm-url]: https://npmjs.org/package/@ohif/app
|
||||||
|
[npm-downloads-image]: https://img.shields.io/npm/dm/@ohif/app.svg?style=flat-square
|
||||||
|
[npm-version-image]: https://img.shields.io/npm/v/@ohif/app.svg?style=flat-square
|
||||||
|
[docker-pulls-img]: https://img.shields.io/docker/pulls/ohif/viewer.svg?style=flat-square
|
||||||
|
[docker-image-url]: https://hub.docker.com/r/ohif/app
|
||||||
|
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
|
||||||
|
[license-url]: LICENSE
|
||||||
|
[percy-image]: https://percy.io/static/images/percy-badge.svg
|
||||||
|
[percy-url]: https://percy.io/Open-Health-Imaging-Foundation/OHIF-Viewer
|
||||||
|
<!-- Links -->
|
||||||
|
[monorepo]: https://en.wikipedia.org/wiki/Monorepo
|
||||||
|
[how-to-fork]: https://help.github.com/en/articles/fork-a-repo
|
||||||
|
[how-to-clone]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork
|
||||||
|
[ohif-architecture]: https://docs.ohif.org/architecture/index.html
|
||||||
|
[ohif-extensions]: https://docs.ohif.org/architecture/index.html
|
||||||
|
[deployment-docs]: https://docs.ohif.org/deployment/
|
||||||
|
[react-url]: https://reactjs.org/
|
||||||
|
[pwa-url]: https://developers.google.com/web/progressive-web-apps/
|
||||||
|
[ohif-viewer-url]: https://www.npmjs.com/package/@ohif/app
|
||||||
|
[configuration-url]: https://docs.ohif.org/configuring/
|
||||||
|
[extensions-url]: https://docs.ohif.org/extensions/
|
||||||
|
<!-- Platform -->
|
||||||
|
[platform-core]: platform/core/README.md
|
||||||
|
[core-npm]: https://www.npmjs.com/package/@ohif/core
|
||||||
|
[platform-i18n]: platform/i18n/README.md
|
||||||
|
[i18n-npm]: https://www.npmjs.com/package/@ohif/i18n
|
||||||
|
[platform-ui]: platform/ui/README.md
|
||||||
|
[ui-npm]: https://www.npmjs.com/package/@ohif/ui
|
||||||
|
[platform-viewer]: platform/app/README.md
|
||||||
|
[viewer-npm]: https://www.npmjs.com/package/@ohif/app
|
||||||
|
<!-- Extensions -->
|
||||||
|
[extension-cornerstone]: extensions/cornerstone/README.md
|
||||||
|
[cornerstone-npm]: https://www.npmjs.com/package/@ohif/extension-cornerstone
|
||||||
|
[extension-dicom-html]: extensions/dicom-html/README.md
|
||||||
|
[html-npm]: https://www.npmjs.com/package/@ohif/extension-dicom-html
|
||||||
|
[extension-dicom-microscopy]: extensions/dicom-microscopy/README.md
|
||||||
|
[microscopy-npm]: https://www.npmjs.com/package/@ohif/extension-dicom-microscopy
|
||||||
|
[extension-dicom-pdf]: extensions/dicom-pdf/README.md
|
||||||
|
[pdf-npm]: https://www.npmjs.com/package/@ohif/extension-dicom-pdf
|
||||||
|
[extension-vtk]: extensions/vtk/README.md
|
||||||
|
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large&issueType=license)
|
||||||
3
addOns/README.md
Normal file
3
addOns/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# External Dependencies
|
||||||
|
|
||||||
|
This module contains optional dependencies and external dependencies for including in OHIF, such as the DICOM Microscopy Viewer component.
|
||||||
539
addOns/externals/devDependencies/CHANGELOG.md
vendored
Normal file
539
addOns/externals/devDependencies/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.60...v3.9.0-beta.61) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.59...v3.9.0-beta.60) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.58...v3.9.0-beta.59) (2024-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.57...v3.9.0-beta.58) (2024-07-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.56...v3.9.0-beta.57) (2024-07-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.55...v3.9.0-beta.56) (2024-07-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.54...v3.9.0-beta.55) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.53...v3.9.0-beta.54) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.52...v3.9.0-beta.53) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.51...v3.9.0-beta.52) (2024-06-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.50...v3.9.0-beta.51) (2024-06-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.49...v3.9.0-beta.50) (2024-06-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.48...v3.9.0-beta.49) (2024-06-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.47...v3.9.0-beta.48) (2024-06-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/devDependencies
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.46...v3.9.0-beta.47) (2024-06-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Allow the mode setup/creation to be async, and provide a few more values to extension/app config/mode setup. ([#4016](https://github.com/OHIF/Viewers/issues/4016)) ([88575c6](https://github.com/OHIF/Viewers/commit/88575c6c09fd778a31b2f91524163ce65d1639dd))
|
||||||
94
addOns/externals/devDependencies/package.json
vendored
Normal file
94
addOns/externals/devDependencies/package.json
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"name": "@externals/devDependencies",
|
||||||
|
"description": "External dev dependencies - put dev build dependencies here",
|
||||||
|
"version": "3.9.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"yarn": ">=1.19.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.13",
|
||||||
|
"@kitware/vtk.js": "32.1.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"core-js": "^3.2.1",
|
||||||
|
"moment": "^2.9.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||||
|
"@svgr/webpack": "^8.1.0",
|
||||||
|
"@types/jest": "^27.5.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||||
|
"@typescript-eslint/parser": "^6.3.0",
|
||||||
|
"autoprefixer": "^10.4.4",
|
||||||
|
"babel-eslint": "9.x",
|
||||||
|
"babel-loader": "^8.2.4",
|
||||||
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"css-loader": "^6.8.1",
|
||||||
|
"dotenv": "^8.1.0",
|
||||||
|
"eslint": "^8.39.0",
|
||||||
|
"eslint-config-prettier": "^7.2.0",
|
||||||
|
"eslint-config-react-app": "^6.0.0",
|
||||||
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"eslint-plugin-flowtype": "^7.0.0",
|
||||||
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"eslint-plugin-promise": "^5.2.0",
|
||||||
|
"eslint-plugin-react": "^7.29.4",
|
||||||
|
"eslint-plugin-react-hooks": "^4.4.0",
|
||||||
|
"eslint-plugin-tsdoc": "^0.2.11",
|
||||||
|
"eslint-webpack-plugin": "^2.5.3",
|
||||||
|
"execa": "^8.0.1",
|
||||||
|
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
||||||
|
"html-webpack-plugin": "^5.3.2",
|
||||||
|
"husky": "^3.0.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"jest-canvas-mock": "^2.1.0",
|
||||||
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
|
"jest-junit": "^6.4.0",
|
||||||
|
"lerna": "^7.2.0",
|
||||||
|
"lint-staged": "^9.0.2",
|
||||||
|
"mini-css-extract-plugin": "^2.1.0",
|
||||||
|
"optimize-css-assets-webpack-plugin": "^6.0.1",
|
||||||
|
"postcss": "^8.3.5",
|
||||||
|
"postcss-import": "^14.0.2",
|
||||||
|
"postcss-loader": "^6.1.1",
|
||||||
|
"postcss-preset-env": "^7.4.3",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||||
|
"react-refresh": "^0.14.2",
|
||||||
|
"semver": "^7.5.1",
|
||||||
|
"serve": "^14.2.0",
|
||||||
|
"shader-loader": "^1.3.1",
|
||||||
|
"shx": "^0.3.3",
|
||||||
|
"source-map-loader": "^4.0.1",
|
||||||
|
"start-server-and-test": "^1.10.0",
|
||||||
|
"style-loader": "^1.0.0",
|
||||||
|
"stylus": "^0.59.0",
|
||||||
|
"stylus-loader": "^7.1.3",
|
||||||
|
"terser-webpack-plugin": "^5.1.4",
|
||||||
|
"typescript": "5.5.4",
|
||||||
|
"unused-webpack-plugin": "2.4.0",
|
||||||
|
"webpack": "5.94.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.8.0",
|
||||||
|
"webpack-cli": "^4.7.2",
|
||||||
|
"webpack-dev-server": "4.7.3",
|
||||||
|
"webpack-hot-middleware": "^2.25.0",
|
||||||
|
"webpack-merge": "^5.7.3",
|
||||||
|
"workbox-webpack-plugin": "^6.1.5",
|
||||||
|
"worker-loader": "^3.0.8"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "Included as direct dependency"
|
||||||
|
}
|
||||||
|
}
|
||||||
539
addOns/externals/dicom-microscopy-viewer/CHANGELOG.md
vendored
Normal file
539
addOns/externals/dicom-microscopy-viewer/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.60...v3.9.0-beta.61) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.59...v3.9.0-beta.60) (2024-07-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.58...v3.9.0-beta.59) (2024-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.57...v3.9.0-beta.58) (2024-07-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.56...v3.9.0-beta.57) (2024-07-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.55...v3.9.0-beta.56) (2024-07-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.54...v3.9.0-beta.55) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.53...v3.9.0-beta.54) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.52...v3.9.0-beta.53) (2024-06-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.51...v3.9.0-beta.52) (2024-06-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.50...v3.9.0-beta.51) (2024-06-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.49...v3.9.0-beta.50) (2024-06-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.48...v3.9.0-beta.49) (2024-06-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.47...v3.9.0-beta.48) (2024-06-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.46...v3.9.0-beta.47) (2024-06-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Allow the mode setup/creation to be async, and provide a few more values to extension/app config/mode setup. ([#4016](https://github.com/OHIF/Viewers/issues/4016)) ([88575c6](https://github.com/OHIF/Viewers/commit/88575c6c09fd778a31b2f91524163ce65d1639dd))
|
||||||
9
addOns/externals/dicom-microscopy-viewer/package.json
vendored
Normal file
9
addOns/externals/dicom-microscopy-viewer/package.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "@externals/dicom-microscopy-viewer",
|
||||||
|
"description": "External reference to dicom-microscopy-viewer",
|
||||||
|
"version": "3.9.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dicom-microscopy-viewer": "^0.46.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
addOns/package.json
Normal file
51
addOns/package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "ohif-monorepo-root",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "yarn@1.22.22",
|
||||||
|
"workspaces": {
|
||||||
|
"packages": [
|
||||||
|
"../platform/i18n",
|
||||||
|
"../platform/core",
|
||||||
|
"../platform/ui",
|
||||||
|
"../platform/ui-next",
|
||||||
|
"../platform/app",
|
||||||
|
"../extensions/*",
|
||||||
|
"../modes/*",
|
||||||
|
"../addOns/externals/*"
|
||||||
|
],
|
||||||
|
"nohoist": [
|
||||||
|
"**/html-minifier-terser"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"preinstall": "cd .. && node preinstall.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.24.7",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||||
|
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||||
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
|
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||||
|
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||||
|
"@babel/plugin-transform-runtime": "7.24.7",
|
||||||
|
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||||
|
"@babel/preset-env": "7.24.7",
|
||||||
|
"@babel/preset-react": "^7.16.7",
|
||||||
|
"@babel/preset-typescript": "^7.13.0"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"**/@babel/runtime": "^7.20.13",
|
||||||
|
"commander": "8.3.0",
|
||||||
|
"dcmjs": ">=0.33.0",
|
||||||
|
"dicomweb-client": ">=0.10.4",
|
||||||
|
"nth-check": "^2.1.1",
|
||||||
|
"trim-newlines": "^5.0.0",
|
||||||
|
"glob-parent": "^6.0.2",
|
||||||
|
"trim": "^1.0.0",
|
||||||
|
"package-json": "^8.1.0",
|
||||||
|
"typescript": "5.5.4",
|
||||||
|
"sharp": "^0.32.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
aliases.config.js
Normal file
8
aliases.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* Used by webpack, babel and eslint */
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'@codinsky/parse-js': path.resolve(__dirname, 'packages/parse/src'),
|
||||||
|
'@codinsky/curate': path.resolve(__dirname, 'packages/curate/src'),
|
||||||
|
};
|
||||||
58
babel.config.js
Normal file
58
babel.config.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// https://babeljs.io/docs/en/options#babelrcroots
|
||||||
|
const { extendDefaultPlugins } = require('svgo');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
|
||||||
|
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
|
||||||
|
plugins: [
|
||||||
|
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||||
|
'@babel/plugin-transform-typescript',
|
||||||
|
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
||||||
|
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||||
|
'@babel/plugin-transform-class-static-block',
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: 'commonjs',
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
'@babel/plugin-transform-regenerator',
|
||||||
|
'@babel/transform-destructuring',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
'@babel/plugin-transform-typescript',
|
||||||
|
'@babel/plugin-transform-class-static-block',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
plugins: ['react-refresh/babel'],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
1
commit.txt
Normal file
1
commit.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
610faa5a2738da5eabc40e57e338c359f481e852
|
||||||
8
eslintAliasesResolver.js
Normal file
8
eslintAliasesResolver.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports.interfaceVersion = 2;
|
||||||
|
|
||||||
|
module.exports.resolve = (source, file, aliases) => {
|
||||||
|
if (aliases[source]) {
|
||||||
|
return { found: true, path: aliases[source] };
|
||||||
|
}
|
||||||
|
return { found: false };
|
||||||
|
};
|
||||||
12
extensions/cornerstone-dicom-pmap/.webpack/webpack.dev.js
Normal file
12
extensions/cornerstone-dicom-pmap/.webpack/webpack.dev.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||||
|
};
|
||||||
54
extensions/cornerstone-dicom-pmap/.webpack/webpack.prod.js
Normal file
54
extensions/cornerstone-dicom-pmap/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const webpack = require('webpack');
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
|
const pkg = require('./../package.json');
|
||||||
|
|
||||||
|
const ROOT_DIR = path.join(__dirname, '../');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputName = `ohif-${pkg.name.split('/').pop()}`;
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||||
|
|
||||||
|
return merge(commonConfig, {
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: true,
|
||||||
|
timings: true,
|
||||||
|
assets: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
modules: false,
|
||||||
|
children: false,
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
sideEffects: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: ROOT_DIR,
|
||||||
|
library: 'ohif-extension-cornerstone-dicom-pmap',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
filename: pkg.main,
|
||||||
|
},
|
||||||
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
|
maxChunks: 1,
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: `./dist/${outputName}.css`,
|
||||||
|
chunkFilename: `./dist/${outputName}.css`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
405
extensions/cornerstone-dicom-pmap/CHANGELOG.md
Normal file
405
extensions/cornerstone-dicom-pmap/CHANGELOG.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **pmap:** added support for parametric map ([#4284](https://github.com/OHIF/Viewers/issues/4284)) ([fc0064f](https://github.com/OHIF/Viewers/commit/fc0064fd9d8cdc8fde81b81f0e71fd5d077ca22b))
|
||||||
20
extensions/cornerstone-dicom-pmap/LICENSE
Normal file
20
extensions/cornerstone-dicom-pmap/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Open Health Imaging Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
12
extensions/cornerstone-dicom-pmap/README.md
Normal file
12
extensions/cornerstone-dicom-pmap/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# dicom-pmap
|
||||||
|
## Description
|
||||||
|
|
||||||
|
DICOM PMAP read workflow. This extension will allow you to load a DICOM Parametric
|
||||||
|
Map image and display it on OHIF.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
OHIF
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT
|
||||||
44
extensions/cornerstone-dicom-pmap/babel.config.js
Normal file
44
extensions/cornerstone-dicom-pmap/babel.config.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: 'commonjs',
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
'@babel/plugin-transform-regenerator',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
plugins: ['react-hot-loader/babel'],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
54
extensions/cornerstone-dicom-pmap/package.json
Normal file
54
extensions/cornerstone-dicom-pmap/package.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||||
|
"version": "3.9.1",
|
||||||
|
"description": "DICOM Parametric Map read workflow",
|
||||||
|
"author": "OHIF",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/ohif-extension-cornerstone-dicom-pmap.umd.js",
|
||||||
|
"module": "src/index.tsx",
|
||||||
|
"files": [
|
||||||
|
"dist/**",
|
||||||
|
"public/**",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"repository": "OHIF/Viewers",
|
||||||
|
"keywords": [
|
||||||
|
"ohif-extension"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.18.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "shx rm -rf dist",
|
||||||
|
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||||
|
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||||
|
"dev:dicom-pmap": "yarn run dev",
|
||||||
|
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||||
|
"build:package-1": "yarn run build",
|
||||||
|
"start": "yarn run dev"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ohif/core": "3.9.1",
|
||||||
|
"@ohif/extension-cornerstone": "3.9.1",
|
||||||
|
"@ohif/extension-default": "3.9.1",
|
||||||
|
"@ohif/i18n": "3.9.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-i18next": "^12.2.2",
|
||||||
|
"react-router": "^6.8.1",
|
||||||
|
"react-router-dom": "^6.8.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.13",
|
||||||
|
"@cornerstonejs/adapters": "^2.2.4",
|
||||||
|
"@cornerstonejs/core": "^2.2.4",
|
||||||
|
"@kitware/vtk.js": "32.1.0",
|
||||||
|
"react-color": "^2.19.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import { utils } from '@ohif/core';
|
||||||
|
import { metaData, cache, utilities as csUtils, volumeLoader } from '@cornerstonejs/core';
|
||||||
|
import { adaptersPMAP } from '@cornerstonejs/adapters';
|
||||||
|
import { SOPClassHandlerId } from './id';
|
||||||
|
import { dicomLoaderService } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
|
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||||
|
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.30'];
|
||||||
|
|
||||||
|
function _getDisplaySetsFromSeries(
|
||||||
|
instances,
|
||||||
|
servicesManager: AppTypes.ServicesManager,
|
||||||
|
extensionManager
|
||||||
|
) {
|
||||||
|
const instance = instances[0];
|
||||||
|
|
||||||
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPClassUID,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
wadoUriRoot,
|
||||||
|
} = instance;
|
||||||
|
|
||||||
|
const displaySet = {
|
||||||
|
// Parametric map use to have the same modality as its referenced volume but
|
||||||
|
// "PMAP" is used in the viewer even though this is not a valid DICOM modality
|
||||||
|
Modality: 'PMAP',
|
||||||
|
isReconstructable: true, // by default for now
|
||||||
|
displaySetInstanceUID: `pmap.${utils.guid()}`,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPClassHandlerId,
|
||||||
|
SOPClassUID,
|
||||||
|
referencedImages: null,
|
||||||
|
referencedSeriesInstanceUID: null,
|
||||||
|
referencedDisplaySetInstanceUID: null,
|
||||||
|
referencedVolumeURI: null,
|
||||||
|
referencedVolumeId: null,
|
||||||
|
isDerivedDisplaySet: true,
|
||||||
|
loadStatus: {
|
||||||
|
loading: false,
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
sopClassUids,
|
||||||
|
instance,
|
||||||
|
instances: [instance],
|
||||||
|
wadoRoot,
|
||||||
|
wadoUriRoot,
|
||||||
|
wadoUri,
|
||||||
|
isOverlayDisplaySet: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
|
|
||||||
|
if (!referencedSeriesSequence) {
|
||||||
|
console.error('ReferencedSeriesSequence is missing for the parametric map');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence;
|
||||||
|
|
||||||
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||||
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||||
|
|
||||||
|
// Does not get the referenced displaySet during parametric displaySet creation
|
||||||
|
// because it is still not available (getDisplaySetByUID returns `undefined`).
|
||||||
|
displaySet.getReferenceDisplaySet = () => {
|
||||||
|
const { displaySetService } = servicesManager.services;
|
||||||
|
|
||||||
|
if (displaySet.referencedDisplaySetInstanceUID) {
|
||||||
|
return displaySetService.getDisplaySetByUID(displaySet.referencedDisplaySetInstanceUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||||
|
displaySet.referencedSeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||||
|
throw new Error('Referenced displaySet is missing for the parametric map');
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedDisplaySet = referencedDisplaySets[0];
|
||||||
|
|
||||||
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||||
|
|
||||||
|
return referencedDisplaySet;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Does not get the referenced volumeId during parametric displaySet creation because the
|
||||||
|
// referenced displaySet is still not available (getDisplaySetByUID returns `undefined`).
|
||||||
|
displaySet.getReferencedVolumeId = () => {
|
||||||
|
if (displaySet.referencedVolumeId) {
|
||||||
|
return displaySet.referencedVolumeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedDisplaySet = displaySet.getReferenceDisplaySet();
|
||||||
|
const referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
||||||
|
const referencedVolumeId = `${VOLUME_LOADER_SCHEME}:${referencedVolumeURI}`;
|
||||||
|
|
||||||
|
displaySet.referencedVolumeURI = referencedVolumeURI;
|
||||||
|
displaySet.referencedVolumeId = referencedVolumeId;
|
||||||
|
|
||||||
|
return referencedVolumeId;
|
||||||
|
};
|
||||||
|
|
||||||
|
displaySet.load = async ({ headers }) =>
|
||||||
|
await _load(displaySet, servicesManager, extensionManager, headers);
|
||||||
|
|
||||||
|
return [displaySet];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRangeFromPixelData = (pixelData: Float32Array) => {
|
||||||
|
let lowest = pixelData[0];
|
||||||
|
let highest = pixelData[0];
|
||||||
|
|
||||||
|
for (let i = 1; i < pixelData.length; i++) {
|
||||||
|
if (pixelData[i] < lowest) {
|
||||||
|
lowest = pixelData[i];
|
||||||
|
}
|
||||||
|
if (pixelData[i] > highest) {
|
||||||
|
highest = pixelData[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [lowest, highest];
|
||||||
|
};
|
||||||
|
|
||||||
|
async function _load(
|
||||||
|
displaySet,
|
||||||
|
servicesManager: AppTypes.ServicesManager,
|
||||||
|
extensionManager,
|
||||||
|
headers
|
||||||
|
) {
|
||||||
|
const volumeId = `${VOLUME_LOADER_SCHEME}:${displaySet.displaySetInstanceUID}`;
|
||||||
|
const volumeLoadObject = cache.getVolumeLoadObject(volumeId);
|
||||||
|
|
||||||
|
if (volumeLoadObject) {
|
||||||
|
return volumeLoadObject.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySet.loading = true;
|
||||||
|
displaySet.isLoaded = false;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const promise = _loadParametricMap({
|
||||||
|
extensionManager,
|
||||||
|
displaySet,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
cache.putVolumeLoadObject(volumeId, { promise }).catch(err => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise
|
||||||
|
.then(() => {
|
||||||
|
displaySet.loading = false;
|
||||||
|
displaySet.isLoaded = true;
|
||||||
|
// Broadcast that loading is complete
|
||||||
|
servicesManager.services.segmentationService._broadcastEvent(
|
||||||
|
servicesManager.services.segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||||
|
{
|
||||||
|
pmapDisplaySet: displaySet,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
displaySet.loading = false;
|
||||||
|
displaySet.isLoaded = false;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _loadParametricMap({ displaySet, headers }: withAppTypes) {
|
||||||
|
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(displaySet, null, headers);
|
||||||
|
const referencedVolumeId = displaySet.getReferencedVolumeId();
|
||||||
|
const cachedReferencedVolume = cache.getVolume(referencedVolumeId);
|
||||||
|
|
||||||
|
// Parametric map can be loaded only if its referenced volume exists otherwise it will fail
|
||||||
|
if (!cachedReferencedVolume) {
|
||||||
|
throw new Error(
|
||||||
|
'Referenced Volume is missing for the PMAP, and stack viewport PMAP is not supported yet'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { imageIds } = cachedReferencedVolume;
|
||||||
|
const results = await adaptersPMAP.Cornerstone3D.ParametricMap.generateToolState(
|
||||||
|
imageIds,
|
||||||
|
arrayBuffer,
|
||||||
|
metaData
|
||||||
|
);
|
||||||
|
const { pixelData } = results;
|
||||||
|
const TypedArrayConstructor = pixelData.constructor;
|
||||||
|
const paramMapId = displaySet.displaySetInstanceUID;
|
||||||
|
|
||||||
|
const derivedVolume = await volumeLoader.createAndCacheDerivedVolume(referencedVolumeId, {
|
||||||
|
volumeId: paramMapId,
|
||||||
|
targetBuffer: {
|
||||||
|
type: TypedArrayConstructor.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newPixelData = new TypedArrayConstructor(pixelData.length);
|
||||||
|
for (let i = 0; i < pixelData.length; i++) {
|
||||||
|
newPixelData[i] = pixelData[i] * 100;
|
||||||
|
}
|
||||||
|
derivedVolume.voxelManager.setCompleteScalarDataArray(newPixelData);
|
||||||
|
const range = getRangeFromPixelData(newPixelData);
|
||||||
|
const windowLevel = csUtils.windowLevel.toWindowLevel(range[0], range[1]);
|
||||||
|
|
||||||
|
derivedVolume.metadata.voiLut = [windowLevel];
|
||||||
|
derivedVolume.loadStatus = { loaded: true };
|
||||||
|
|
||||||
|
return derivedVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
|
const getDisplaySetsFromSeries = instances => {
|
||||||
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'dicom-pmap',
|
||||||
|
sopClassUids,
|
||||||
|
getDisplaySetsFromSeries,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSopClassHandlerModule;
|
||||||
7
extensions/cornerstone-dicom-pmap/src/id.js
Normal file
7
extensions/cornerstone-dicom-pmap/src/id.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
|
const id = packageJson.name;
|
||||||
|
const SOPClassHandlerName = 'dicom-pmap';
|
||||||
|
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||||
|
|
||||||
|
export { id, SOPClassHandlerId, SOPClassHandlerName };
|
||||||
39
extensions/cornerstone-dicom-pmap/src/index.tsx
Normal file
39
extensions/cornerstone-dicom-pmap/src/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { id } from './id';
|
||||||
|
import React from 'react';
|
||||||
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
|
|
||||||
|
const Component = React.lazy(() => {
|
||||||
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstonePMAPViewport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const OHIFCornerstonePMAPViewport = props => {
|
||||||
|
return (
|
||||||
|
<React.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Component {...props} />
|
||||||
|
</React.Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can remove any of the following modules if you don't need them.
|
||||||
|
*/
|
||||||
|
const extension = {
|
||||||
|
id,
|
||||||
|
getViewportModule({ servicesManager, extensionManager, commandsManager }) {
|
||||||
|
const ExtendedOHIFCornerstonePMAPViewport = props => {
|
||||||
|
return (
|
||||||
|
<OHIFCornerstonePMAPViewport
|
||||||
|
servicesManager={servicesManager}
|
||||||
|
extensionManager={extensionManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [{ name: 'dicom-pmap', component: ExtendedOHIFCornerstonePMAPViewport }];
|
||||||
|
},
|
||||||
|
getSopClassHandlerModule,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default extension;
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
|
||||||
|
|
||||||
|
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
|
||||||
|
const {
|
||||||
|
displaySets,
|
||||||
|
children,
|
||||||
|
viewportOptions,
|
||||||
|
displaySetOptions,
|
||||||
|
servicesManager,
|
||||||
|
extensionManager,
|
||||||
|
} = props;
|
||||||
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
const { displaySetService, segmentationService, uiNotificationService } =
|
||||||
|
servicesManager.services;
|
||||||
|
|
||||||
|
// PMAP viewport will always have a single display set
|
||||||
|
if (displaySets.length !== 1) {
|
||||||
|
throw new Error('PMAP viewport must have a single display set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pmapDisplaySet = displaySets[0];
|
||||||
|
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||||
|
const referencedDisplaySetRef = useRef(null);
|
||||||
|
const { viewports, activeViewportId } = viewportGrid;
|
||||||
|
const referencedDisplaySet = pmapDisplaySet.getReferenceDisplaySet();
|
||||||
|
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
||||||
|
referencedDisplaySet,
|
||||||
|
pmapDisplaySet
|
||||||
|
);
|
||||||
|
|
||||||
|
referencedDisplaySetRef.current = {
|
||||||
|
displaySet: referencedDisplaySet,
|
||||||
|
metadata: referencedDisplaySetMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [pmapIsLoading, setPmapIsLoading] = useState(!pmapDisplaySet.isLoaded);
|
||||||
|
|
||||||
|
// Add effect to listen for loading complete
|
||||||
|
useEffect(() => {
|
||||||
|
const { unsubscribe } = segmentationService.subscribe(
|
||||||
|
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||||
|
evt => {
|
||||||
|
if (evt.pmapDisplaySet?.displaySetInstanceUID === pmapDisplaySet.displaySetInstanceUID) {
|
||||||
|
setPmapIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [pmapDisplaySet]);
|
||||||
|
|
||||||
|
const getCornerstoneViewport = useCallback(() => {
|
||||||
|
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||||
|
const { component: Component } = extensionManager.getModuleEntry(
|
||||||
|
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||||
|
);
|
||||||
|
|
||||||
|
displaySetOptions.unshift({});
|
||||||
|
const [pmapDisplaySetOptions] = displaySetOptions;
|
||||||
|
|
||||||
|
// Make sure `options` exists
|
||||||
|
pmapDisplaySetOptions.options = pmapDisplaySetOptions.options ?? {};
|
||||||
|
|
||||||
|
Object.assign(pmapDisplaySetOptions.options, {
|
||||||
|
colormap: {
|
||||||
|
name: 'rainbow_2',
|
||||||
|
opacity: [
|
||||||
|
{ value: 0, opacity: 0 },
|
||||||
|
{ value: 0.25, opacity: 0.25 },
|
||||||
|
{ value: 0.5, opacity: 0.5 },
|
||||||
|
{ value: 0.75, opacity: 0.75 },
|
||||||
|
{ value: 0.9, opacity: 0.99 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
voi: {
|
||||||
|
windowCenter: 50,
|
||||||
|
windowWidth: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
uiNotificationService.show({
|
||||||
|
title: 'Parametric Map',
|
||||||
|
type: 'warning',
|
||||||
|
message: 'The values are multiplied by 100 in the viewport for better visibility',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...props}
|
||||||
|
// Referenced + PMAP displaySets must be passed as parameter in this order
|
||||||
|
displaySets={[referencedDisplaySet, pmapDisplaySet]}
|
||||||
|
viewportOptions={{
|
||||||
|
viewportType: 'volume',
|
||||||
|
orientation: viewportOptions.orientation,
|
||||||
|
viewportId: viewportOptions.viewportId,
|
||||||
|
}}
|
||||||
|
displaySetOptions={[{}, pmapDisplaySetOptions]}
|
||||||
|
></Component>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
extensionManager,
|
||||||
|
displaySetOptions,
|
||||||
|
props,
|
||||||
|
pmapDisplaySet,
|
||||||
|
viewportOptions.orientation,
|
||||||
|
viewportOptions.viewportId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Cleanup the PMAP viewport when the viewport is destroyed
|
||||||
|
useEffect(() => {
|
||||||
|
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||||
|
({ displaySetInstanceUIDs }) => {
|
||||||
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
|
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||||
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
|
viewportId: activeViewportId,
|
||||||
|
displaySetInstanceUIDs: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
onDisplaySetsRemovedSubscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [activeViewportId, displaySetService, viewportGridService, viewports]);
|
||||||
|
|
||||||
|
let childrenWithProps = null;
|
||||||
|
|
||||||
|
if (children && children.length) {
|
||||||
|
childrenWithProps = children.map((child, index) => {
|
||||||
|
return (
|
||||||
|
child &&
|
||||||
|
React.cloneElement(child, {
|
||||||
|
viewportId,
|
||||||
|
key: index,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||||
|
{pmapIsLoading && (
|
||||||
|
<LoadingIndicatorTotalPercent
|
||||||
|
className="h-full w-full"
|
||||||
|
totalNumbers={null}
|
||||||
|
percentComplete={null}
|
||||||
|
loadingText="Loading Parametric Map..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{getCornerstoneViewport()}
|
||||||
|
{childrenWithProps}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIFCornerstonePMAPViewport.propTypes = {
|
||||||
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
viewportId: PropTypes.string.isRequired,
|
||||||
|
dataSource: PropTypes.object,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
function _getReferencedDisplaySetMetadata(referencedDisplaySet, pmapDisplaySet) {
|
||||||
|
const { SharedFunctionalGroupsSequence } = pmapDisplaySet.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 referencedDisplaySetMetadata = {
|
||||||
|
PatientID: image0.PatientID,
|
||||||
|
PatientName: image0.PatientName,
|
||||||
|
PatientSex: image0.PatientSex,
|
||||||
|
PatientAge: image0.PatientAge,
|
||||||
|
SliceThickness: image0.SliceThickness || SliceThickness,
|
||||||
|
StudyDate: image0.StudyDate,
|
||||||
|
SeriesDescription: image0.SeriesDescription,
|
||||||
|
SeriesInstanceUID: image0.SeriesInstanceUID,
|
||||||
|
SeriesNumber: image0.SeriesNumber,
|
||||||
|
ManufacturerModelName: image0.ManufacturerModelName,
|
||||||
|
SpacingBetweenSlices: image0.SpacingBetweenSlices || SpacingBetweenSlices,
|
||||||
|
};
|
||||||
|
|
||||||
|
return referencedDisplaySetMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OHIFCornerstonePMAPViewport;
|
||||||
12
extensions/cornerstone-dicom-rt/.webpack/webpack.dev.js
Normal file
12
extensions/cornerstone-dicom-rt/.webpack/webpack.dev.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||||
|
};
|
||||||
48
extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js
Normal file
48
extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const webpack = require('webpack');
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const pkg = require('./../package.json');
|
||||||
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
|
||||||
|
const ROOT_DIR = path.join(__dirname, './..');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
const commonConfig = webpackCommon(env, argv, { SRC_DIR, ENTRY, DIST_DIR });
|
||||||
|
|
||||||
|
return merge(commonConfig, {
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: true,
|
||||||
|
timings: true,
|
||||||
|
assets: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
modules: false,
|
||||||
|
children: false,
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
sideEffects: false,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: ROOT_DIR,
|
||||||
|
library: 'ohif-extension-cornerstone-dicom-rt',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
filename: pkg.main,
|
||||||
|
},
|
||||||
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
|
maxChunks: 1,
|
||||||
|
}),
|
||||||
|
// new BundleAnalyzerPlugin(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
2146
extensions/cornerstone-dicom-rt/CHANGELOG.md
Normal file
2146
extensions/cornerstone-dicom-rt/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
20
extensions/cornerstone-dicom-rt/LICENSE
Normal file
20
extensions/cornerstone-dicom-rt/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Open Health Imaging Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
13
extensions/cornerstone-dicom-rt/README.md
Normal file
13
extensions/cornerstone-dicom-rt/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# dicom-rt
|
||||||
|
## Description
|
||||||
|
|
||||||
|
DICOM RT read workflow. This extension will allow you to load a DICOM RTSS image
|
||||||
|
and display it in OHIF.
|
||||||
|
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
OHIF
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT
|
||||||
44
extensions/cornerstone-dicom-rt/babel.config.js
Normal file
44
extensions/cornerstone-dicom-rt/babel.config.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: 'commonjs',
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
'@babel/plugin-transform-regenerator',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
plugins: ['react-refresh/babel'],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
51
extensions/cornerstone-dicom-rt/package.json
Normal file
51
extensions/cornerstone-dicom-rt/package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||||
|
"version": "3.9.1",
|
||||||
|
"description": "DICOM RT read workflow",
|
||||||
|
"author": "OHIF",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/ohif-extension-cornerstone-dicom-rt.umd.js",
|
||||||
|
"module": "src/index.tsx",
|
||||||
|
"files": [
|
||||||
|
"dist/**",
|
||||||
|
"public/**",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": "OHIF/Viewers",
|
||||||
|
"keywords": [
|
||||||
|
"ohif-extension"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.18.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "shx rm -rf dist",
|
||||||
|
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||||
|
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||||
|
"dev:dicom-seg": "yarn run dev",
|
||||||
|
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||||
|
"build:package-1": "yarn run build",
|
||||||
|
"start": "yarn run dev"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ohif/core": "3.9.1",
|
||||||
|
"@ohif/extension-cornerstone": "3.9.1",
|
||||||
|
"@ohif/extension-default": "3.9.1",
|
||||||
|
"@ohif/i18n": "3.9.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-i18next": "^10.11.0",
|
||||||
|
"react-router": "^6.23.1",
|
||||||
|
"react-router-dom": "^6.23.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.13",
|
||||||
|
"react-color": "^2.19.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
extensions/cornerstone-dicom-rt/src/getCommandsModule.ts
Normal file
55
extensions/cornerstone-dicom-rt/src/getCommandsModule.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||||
|
|
||||||
|
const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||||
|
const services = servicesManager.services;
|
||||||
|
const { displaySetService, viewportGridService } = services;
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
hydrateRTSDisplaySet: ({ displaySet, viewportId }) => {
|
||||||
|
if (displaySet.Modality !== 'RTSTRUCT') {
|
||||||
|
throw new Error('Display set is not an RTSTRUCT');
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||||
|
displaySet.referencedDisplaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the previously stored segmentationPresentation with the new viewportId
|
||||||
|
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||||
|
// it will have the correct segmentation representation hydrated
|
||||||
|
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||||
|
displaySet: displaySet,
|
||||||
|
type: SegmentationRepresentations.Contour,
|
||||||
|
});
|
||||||
|
|
||||||
|
// update the previously stored positionPresentation with the new viewportId
|
||||||
|
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||||
|
// it will be in the correct position zoom and pan
|
||||||
|
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||||
|
viewportId,
|
||||||
|
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
|
viewportId,
|
||||||
|
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const definitions = {
|
||||||
|
hydrateRTSDisplaySet: {
|
||||||
|
commandFn: actions.hydrateRTSDisplaySet,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
definitions,
|
||||||
|
defaultContext: 'cornerstone-dicom-rt',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default commandsModule;
|
||||||
199
extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts
Normal file
199
extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
import { SOPClassHandlerId } from './id';
|
||||||
|
import loadRTStruct from './loadRTStruct';
|
||||||
|
|
||||||
|
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3'];
|
||||||
|
|
||||||
|
const loadPromises = {};
|
||||||
|
|
||||||
|
function _getDisplaySetsFromSeries(
|
||||||
|
instances,
|
||||||
|
servicesManager: AppTypes.ServicesManager,
|
||||||
|
extensionManager
|
||||||
|
) {
|
||||||
|
const instance = instances[0];
|
||||||
|
|
||||||
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPClassUID,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
wadoUriRoot,
|
||||||
|
} = instance;
|
||||||
|
|
||||||
|
const displaySet = {
|
||||||
|
Modality: 'RTSTRUCT',
|
||||||
|
loading: false,
|
||||||
|
isReconstructable: false, // by default for now since it is a volumetric SEG currently
|
||||||
|
displaySetInstanceUID: utils.guid(),
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPClassHandlerId,
|
||||||
|
SOPClassUID,
|
||||||
|
referencedImages: null,
|
||||||
|
referencedSeriesInstanceUID: null,
|
||||||
|
referencedDisplaySetInstanceUID: null,
|
||||||
|
isDerivedDisplaySet: true,
|
||||||
|
isLoaded: false,
|
||||||
|
isHydrated: false,
|
||||||
|
structureSet: null,
|
||||||
|
sopClassUids,
|
||||||
|
instance,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUriRoot,
|
||||||
|
wadoUri,
|
||||||
|
isOverlayDisplaySet: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
|
if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) {
|
||||||
|
instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
||||||
|
instance.ReferencedFrameOfReferenceSequence
|
||||||
|
);
|
||||||
|
referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!referencedSeriesSequence) {
|
||||||
|
throw new Error('ReferencedSeriesSequence is missing for the RTSTRUCT');
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedSeries = referencedSeriesSequence[0];
|
||||||
|
|
||||||
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||||
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||||
|
|
||||||
|
const { displaySetService } = servicesManager.services;
|
||||||
|
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||||
|
displaySet.referencedSeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||||
|
// Instead of throwing error, subscribe to display sets added
|
||||||
|
const { unsubscribe } = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||||
|
({ displaySetsAdded }) => {
|
||||||
|
const addedDisplaySet = displaySetsAdded[0];
|
||||||
|
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||||
|
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const referencedDisplaySet = referencedDisplaySets[0];
|
||||||
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers);
|
||||||
|
|
||||||
|
return [displaySet];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _load(rtDisplaySet, servicesManager: AppTypes.ServicesManager, extensionManager, headers) {
|
||||||
|
const { SOPInstanceUID } = rtDisplaySet;
|
||||||
|
const { segmentationService } = servicesManager.services;
|
||||||
|
if (
|
||||||
|
(rtDisplaySet.loading || rtDisplaySet.isLoaded) &&
|
||||||
|
loadPromises[SOPInstanceUID] &&
|
||||||
|
_segmentationExistsInCache(rtDisplaySet, segmentationService)
|
||||||
|
) {
|
||||||
|
return loadPromises[SOPInstanceUID];
|
||||||
|
}
|
||||||
|
|
||||||
|
rtDisplaySet.loading = true;
|
||||||
|
|
||||||
|
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
||||||
|
// and also return the same promise to any other callers.
|
||||||
|
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
||||||
|
if (!rtDisplaySet.structureSet) {
|
||||||
|
const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers);
|
||||||
|
|
||||||
|
rtDisplaySet.structureSet = structureSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentationService
|
||||||
|
.createSegmentationForRTDisplaySet(rtDisplaySet)
|
||||||
|
.then(() => {
|
||||||
|
rtDisplaySet.loading = false;
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
rtDisplaySet.loading = false;
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return loadPromises[SOPInstanceUID];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
||||||
|
ReferencedFrameOfReferenceSequence
|
||||||
|
) {
|
||||||
|
const ReferencedSeriesSequence = [];
|
||||||
|
|
||||||
|
ReferencedFrameOfReferenceSequence.forEach(referencedFrameOfReference => {
|
||||||
|
const { RTReferencedStudySequence } = referencedFrameOfReference;
|
||||||
|
|
||||||
|
RTReferencedStudySequence.forEach(rtReferencedStudy => {
|
||||||
|
const { RTReferencedSeriesSequence } = rtReferencedStudy;
|
||||||
|
|
||||||
|
RTReferencedSeriesSequence.forEach(rtReferencedSeries => {
|
||||||
|
const ReferencedInstanceSequence = [];
|
||||||
|
const { ContourImageSequence, SeriesInstanceUID } = rtReferencedSeries;
|
||||||
|
|
||||||
|
ContourImageSequence.forEach(contourImage => {
|
||||||
|
ReferencedInstanceSequence.push({
|
||||||
|
ReferencedSOPInstanceUID: contourImage.ReferencedSOPInstanceUID,
|
||||||
|
ReferencedSOPClassUID: contourImage.ReferencedSOPClassUID,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const referencedSeries = {
|
||||||
|
SeriesInstanceUID,
|
||||||
|
ReferencedInstanceSequence,
|
||||||
|
};
|
||||||
|
|
||||||
|
ReferencedSeriesSequence.push(referencedSeries);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return ReferencedSeriesSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _segmentationExistsInCache(
|
||||||
|
rtDisplaySet,
|
||||||
|
segmentationService: AppTypes.SegmentationService
|
||||||
|
) {
|
||||||
|
// Todo: fix this
|
||||||
|
return false;
|
||||||
|
// This should be abstracted with the CornerstoneCacheService
|
||||||
|
const rtContourId = rtDisplaySet.displaySetInstanceUID;
|
||||||
|
const contour = segmentationService.getContour(rtContourId);
|
||||||
|
|
||||||
|
return contour !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'dicom-rt',
|
||||||
|
sopClassUids,
|
||||||
|
getDisplaySetsFromSeries: instances => {
|
||||||
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSopClassHandlerModule;
|
||||||
7
extensions/cornerstone-dicom-rt/src/id.js
Normal file
7
extensions/cornerstone-dicom-rt/src/id.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
|
const id = packageJson.name;
|
||||||
|
const SOPClassHandlerName = 'dicom-rt';
|
||||||
|
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||||
|
|
||||||
|
export { id, SOPClassHandlerId, SOPClassHandlerName };
|
||||||
63
extensions/cornerstone-dicom-rt/src/index.tsx
Normal file
63
extensions/cornerstone-dicom-rt/src/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { id } from './id';
|
||||||
|
import React from 'react';
|
||||||
|
import { Types } from '@ohif/core';
|
||||||
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
|
import getCommandsModule from './getCommandsModule';
|
||||||
|
|
||||||
|
const Component = React.lazy(() => {
|
||||||
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const OHIFCornerstoneRTViewport = props => {
|
||||||
|
return (
|
||||||
|
<React.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Component {...props} />
|
||||||
|
</React.Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can remove any of the following modules if you don't need them.
|
||||||
|
*/
|
||||||
|
const extension: Types.Extensions.Extension = {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
* You ID can be anything you want, but it should be unique.
|
||||||
|
*/
|
||||||
|
id,
|
||||||
|
getCommandsModule,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PanelModule should provide a list of panels that will be available in OHIF
|
||||||
|
* for Modes to consume and render. Each panel is defined by a {name,
|
||||||
|
* iconName, iconLabel, label, component} object. Example of a panel module
|
||||||
|
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
||||||
|
*/
|
||||||
|
getViewportModule({
|
||||||
|
servicesManager,
|
||||||
|
extensionManager,
|
||||||
|
commandsManager,
|
||||||
|
}: Types.Extensions.ExtensionParams) {
|
||||||
|
const ExtendedOHIFCornerstoneRTViewport = props => {
|
||||||
|
return (
|
||||||
|
<OHIFCornerstoneRTViewport
|
||||||
|
servicesManager={servicesManager}
|
||||||
|
extensionManager={extensionManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [{ name: 'dicom-rt', component: ExtendedOHIFCornerstoneRTViewport }];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
||||||
|
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
||||||
|
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
||||||
|
* Examples include the default sop class handler provided by the default extension
|
||||||
|
*/
|
||||||
|
getSopClassHandlerModule,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default extension;
|
||||||
259
extensions/cornerstone-dicom-rt/src/loadRTStruct.js
Normal file
259
extensions/cornerstone-dicom-rt/src/loadRTStruct.js
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
|
||||||
|
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
|
||||||
|
|
||||||
|
async function checkAndLoadContourData(instance, datasource) {
|
||||||
|
if (!instance || !instance.ROIContourSequence) {
|
||||||
|
return Promise.reject('Invalid instance object or ROIContourSequence');
|
||||||
|
}
|
||||||
|
|
||||||
|
const promisesMap = new Map();
|
||||||
|
|
||||||
|
for (const ROIContour of instance.ROIContourSequence) {
|
||||||
|
const referencedROINumber = ROIContour.ReferencedROINumber;
|
||||||
|
if (!ROIContour || !ROIContour.ContourSequence) {
|
||||||
|
promisesMap.set(referencedROINumber, [Promise.resolve([])]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Contour of ROIContour.ContourSequence) {
|
||||||
|
if (!Contour || !Contour.ContourData) {
|
||||||
|
return Promise.reject('Invalid Contour or ContourData');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contourData = Contour.ContourData;
|
||||||
|
|
||||||
|
if (Array.isArray(contourData)) {
|
||||||
|
promisesMap.has(referencedROINumber)
|
||||||
|
? promisesMap.get(referencedROINumber).push(Promise.resolve(contourData))
|
||||||
|
: promisesMap.set(referencedROINumber, [Promise.resolve(contourData)]);
|
||||||
|
} else if (contourData && contourData.BulkDataURI) {
|
||||||
|
const bulkDataURI = contourData.BulkDataURI;
|
||||||
|
|
||||||
|
if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) {
|
||||||
|
return Promise.reject('Invalid datasource object or retrieve function');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkDataPromise = datasource.retrieve.bulkDataURI({
|
||||||
|
BulkDataURI: bulkDataURI,
|
||||||
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
promisesMap.has(referencedROINumber)
|
||||||
|
? promisesMap.get(referencedROINumber).push(bulkDataPromise)
|
||||||
|
: promisesMap.set(referencedROINumber, [bulkDataPromise]);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(`Invalid ContourData: ${contourData}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPromisesMap = new Map();
|
||||||
|
for (const [key, promiseArray] of promisesMap.entries()) {
|
||||||
|
resolvedPromisesMap.set(key, await Promise.allSettled(promiseArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.ROIContourSequence.forEach(ROIContour => {
|
||||||
|
try {
|
||||||
|
const referencedROINumber = ROIContour.ReferencedROINumber;
|
||||||
|
const resolvedPromises = resolvedPromisesMap.get(referencedROINumber);
|
||||||
|
|
||||||
|
if (ROIContour.ContourSequence) {
|
||||||
|
ROIContour.ContourSequence.forEach((Contour, index) => {
|
||||||
|
const promise = resolvedPromises[index];
|
||||||
|
if (promise.status === 'fulfilled') {
|
||||||
|
if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) {
|
||||||
|
// If promise.value is already an array of numbers, use it directly
|
||||||
|
Contour.ContourData = promise.value;
|
||||||
|
} else {
|
||||||
|
// If the resolved promise value is a byte array (Blob), it needs to be decoded
|
||||||
|
const uint8Array = new Uint8Array(promise.value);
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
const dataUint8Array = textDecoder.decode(uint8Array);
|
||||||
|
if (typeof dataUint8Array === 'string' && dataUint8Array.includes('\\')) {
|
||||||
|
Contour.ContourData = dataUint8Array.split('\\').map(parseFloat);
|
||||||
|
} else {
|
||||||
|
Contour.ContourData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(promise.reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function loadRTStruct(extensionManager, rtStructDisplaySet, headers) {
|
||||||
|
const utilityModule = extensionManager.getModuleEntry(
|
||||||
|
'@ohif/extension-cornerstone.utilityModule.common'
|
||||||
|
);
|
||||||
|
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||||
|
const { bulkDataURI } = dataSource.getConfig?.() || {};
|
||||||
|
|
||||||
|
const { dicomLoaderService } = utilityModule.exports;
|
||||||
|
|
||||||
|
// Set here is loading is asynchronous.
|
||||||
|
// If this function throws its set back to false.
|
||||||
|
rtStructDisplaySet.isLoaded = true;
|
||||||
|
let instance = rtStructDisplaySet.instance;
|
||||||
|
|
||||||
|
if (!bulkDataURI || !bulkDataURI.enabled) {
|
||||||
|
const segArrayBuffer = await dicomLoaderService.findDicomDataPromise(
|
||||||
|
rtStructDisplaySet,
|
||||||
|
null,
|
||||||
|
headers
|
||||||
|
);
|
||||||
|
|
||||||
|
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
||||||
|
const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||||
|
rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||||
|
instance = rtStructDataset;
|
||||||
|
} else {
|
||||||
|
await checkAndLoadContourData(instance, dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance;
|
||||||
|
|
||||||
|
// Define our structure set entry and add it to the rtstruct module state.
|
||||||
|
const structureSet = {
|
||||||
|
StructureSetLabel: instance.StructureSetLabel,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
ROIContours: [],
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||||
|
const ROIContour = ROIContourSequence[i];
|
||||||
|
const { ContourSequence } = ROIContour;
|
||||||
|
|
||||||
|
if (!ContourSequence) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSupported = false;
|
||||||
|
|
||||||
|
const ContourSequenceArray = _toArray(ContourSequence);
|
||||||
|
|
||||||
|
const contourPoints = [];
|
||||||
|
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
||||||
|
const { ContourData, NumberOfContourPoints, ContourGeometricType } = ContourSequenceArray[c];
|
||||||
|
|
||||||
|
let isSupported = false;
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
|
||||||
|
points.push({
|
||||||
|
x: ContourData[p],
|
||||||
|
y: ContourData[p + 1],
|
||||||
|
z: ContourData[p + 2],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ContourGeometricType) {
|
||||||
|
case 'CLOSED_PLANAR':
|
||||||
|
case 'OPEN_PLANAR':
|
||||||
|
case 'POINT':
|
||||||
|
isSupported = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
contourPoints.push({
|
||||||
|
numberOfPoints: NumberOfContourPoints,
|
||||||
|
points,
|
||||||
|
type: ContourGeometricType,
|
||||||
|
isSupported,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_setROIContourMetadata(
|
||||||
|
structureSet,
|
||||||
|
StructureSetROISequence,
|
||||||
|
RTROIObservationsSequence,
|
||||||
|
ROIContour,
|
||||||
|
contourPoints,
|
||||||
|
isSupported
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return structureSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setROIContourMetadata(
|
||||||
|
structureSet,
|
||||||
|
StructureSetROISequence,
|
||||||
|
RTROIObservationsSequence,
|
||||||
|
ROIContour,
|
||||||
|
contourPoints,
|
||||||
|
isSupported
|
||||||
|
) {
|
||||||
|
const StructureSetROI = StructureSetROISequence.find(
|
||||||
|
structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber
|
||||||
|
);
|
||||||
|
|
||||||
|
const ROIContourData = {
|
||||||
|
ROINumber: StructureSetROI.ROINumber,
|
||||||
|
ROIName: StructureSetROI.ROIName,
|
||||||
|
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
|
||||||
|
ROIDescription: StructureSetROI.ROIDescription,
|
||||||
|
isSupported,
|
||||||
|
contourPoints,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
_setROIContourDataColor(ROIContour, ROIContourData);
|
||||||
|
|
||||||
|
if (RTROIObservationsSequence) {
|
||||||
|
// If present, add additional RTROIObservations metadata.
|
||||||
|
_setROIContourRTROIObservations(
|
||||||
|
ROIContourData,
|
||||||
|
RTROIObservationsSequence,
|
||||||
|
ROIContour.ReferencedROINumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
structureSet.ROIContours.push(ROIContourData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setROIContourDataColor(ROIContour, ROIContourData) {
|
||||||
|
let { ROIDisplayColor, RecommendedDisplayCIELabValue } = ROIContour;
|
||||||
|
|
||||||
|
if (!ROIDisplayColor && RecommendedDisplayCIELabValue) {
|
||||||
|
// If ROIDisplayColor is absent, try using the RecommendedDisplayCIELabValue color.
|
||||||
|
ROIDisplayColor = dicomlab2RGB(RecommendedDisplayCIELabValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ROIDisplayColor) {
|
||||||
|
ROIContourData.colorArray = [...ROIDisplayColor];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequence, ROINumber) {
|
||||||
|
const RTROIObservations = RTROIObservationsSequence.find(
|
||||||
|
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (RTROIObservations) {
|
||||||
|
// Deep copy so we don't keep the reference to the dcmjs dataset entry.
|
||||||
|
const { ObservationNumber, ROIObservationDescription, RTROIInterpretedType, ROIInterpreter } =
|
||||||
|
RTROIObservations;
|
||||||
|
|
||||||
|
ROIContourData.RTROIObservations = {
|
||||||
|
ObservationNumber,
|
||||||
|
ROIObservationDescription,
|
||||||
|
RTROIInterpretedType,
|
||||||
|
ROIInterpreter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _toArray(objOrArray) {
|
||||||
|
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
|
||||||
|
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
||||||
|
|
||||||
|
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createRTToolGroupAndAddTools;
|
||||||
82
extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
Normal file
82
extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { ButtonEnums } from '@ohif/ui';
|
||||||
|
|
||||||
|
const RESPONSE = {
|
||||||
|
NO_NEVER: -1,
|
||||||
|
CANCEL: 0,
|
||||||
|
HYDRATE_SEG: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
function promptHydrateRT({
|
||||||
|
servicesManager,
|
||||||
|
rtDisplaySet,
|
||||||
|
viewportId,
|
||||||
|
preHydrateCallbacks,
|
||||||
|
hydrateRTDisplaySet,
|
||||||
|
}: withAppTypes) {
|
||||||
|
const { uiViewportDialogService } = servicesManager.services;
|
||||||
|
const extensionManager = servicesManager._extensionManager;
|
||||||
|
const appConfig = extensionManager._appConfig;
|
||||||
|
return new Promise(async function (resolve, reject) {
|
||||||
|
const promptResult = appConfig?.disableConfirmationPrompts
|
||||||
|
? RESPONSE.HYDRATE_SEG
|
||||||
|
: await _askHydrate(uiViewportDialogService, viewportId);
|
||||||
|
|
||||||
|
if (promptResult === RESPONSE.HYDRATE_SEG) {
|
||||||
|
preHydrateCallbacks?.forEach(callback => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
const isHydrated = await hydrateRTDisplaySet({
|
||||||
|
rtDisplaySet,
|
||||||
|
viewportId,
|
||||||
|
servicesManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(isHydrated);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _askHydrate(uiViewportDialogService: AppTypes.UIViewportDialogService, viewportId) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
const message = 'Do you want to open this Segmentation?';
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
id: 'no-hydrate',
|
||||||
|
type: ButtonEnums.type.secondary,
|
||||||
|
text: 'No',
|
||||||
|
value: RESPONSE.CANCEL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'yes-hydrate',
|
||||||
|
type: ButtonEnums.type.primary,
|
||||||
|
text: 'Yes',
|
||||||
|
value: RESPONSE.HYDRATE_SEG,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const onSubmit = result => {
|
||||||
|
uiViewportDialogService.hide();
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
uiViewportDialogService.show({
|
||||||
|
id: 'promptHydrateRT',
|
||||||
|
viewportId,
|
||||||
|
type: 'info',
|
||||||
|
message,
|
||||||
|
actions,
|
||||||
|
onSubmit,
|
||||||
|
onOutsideClick: () => {
|
||||||
|
uiViewportDialogService.hide();
|
||||||
|
resolve(RESPONSE.CANCEL);
|
||||||
|
},
|
||||||
|
onKeyPress: event => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
onSubmit(RESPONSE.HYDRATE_SEG);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default promptHydrateRT;
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useViewportGrid, LoadingIndicatorTotalPercent, ViewportActionArrows } from '@ohif/ui';
|
||||||
|
|
||||||
|
import promptHydrateRT from '../utils/promptHydrateRT';
|
||||||
|
import _getStatusComponent from './_getStatusComponent';
|
||||||
|
|
||||||
|
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
||||||
|
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||||
|
|
||||||
|
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
||||||
|
|
||||||
|
function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
displaySets,
|
||||||
|
viewportOptions,
|
||||||
|
servicesManager,
|
||||||
|
extensionManager,
|
||||||
|
commandsManager,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
displaySetService,
|
||||||
|
toolGroupService,
|
||||||
|
segmentationService,
|
||||||
|
uiNotificationService,
|
||||||
|
customizationService,
|
||||||
|
viewportActionCornersService,
|
||||||
|
} = servicesManager.services;
|
||||||
|
|
||||||
|
const viewportId = viewportOptions.viewportId;
|
||||||
|
|
||||||
|
const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`;
|
||||||
|
|
||||||
|
// RT viewport will always have a single display set
|
||||||
|
if (displaySets.length > 1) {
|
||||||
|
throw new Error('RT viewport should only have a single display set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rtDisplaySet = displaySets[0];
|
||||||
|
|
||||||
|
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||||
|
|
||||||
|
// States
|
||||||
|
const [isToolGroupCreated, setToolGroupCreated] = useState(false);
|
||||||
|
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||||
|
|
||||||
|
// Hydration means that the RT is opened and segments are loaded into the
|
||||||
|
// segmentation panel, and RT is also rendered on any viewport that is in the
|
||||||
|
// same frameOfReferenceUID as the referencedSeriesUID of the RT. However,
|
||||||
|
// loading basically means RT loading over network and bit unpacking of the
|
||||||
|
// RT data.
|
||||||
|
const [isHydrated, setIsHydrated] = useState(rtDisplaySet.isHydrated);
|
||||||
|
const [rtIsLoading, setRtIsLoading] = useState(!rtDisplaySet.isLoaded);
|
||||||
|
const [element, setElement] = useState(null);
|
||||||
|
const [processingProgress, setProcessingProgress] = useState({
|
||||||
|
percentComplete: null,
|
||||||
|
totalSegments: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// refs
|
||||||
|
const referencedDisplaySetRef = useRef(null);
|
||||||
|
|
||||||
|
const { viewports, activeViewportId } = viewportGrid;
|
||||||
|
|
||||||
|
const referencedDisplaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID;
|
||||||
|
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||||
|
referencedDisplaySetInstanceUID
|
||||||
|
);
|
||||||
|
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet);
|
||||||
|
|
||||||
|
referencedDisplaySetRef.current = {
|
||||||
|
displaySet: referencedDisplaySet,
|
||||||
|
metadata: referencedDisplaySetMetadata,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* OnElementEnabled callback which is called after the cornerstoneExtension
|
||||||
|
* has enabled the element. Note: we delegate all the image rendering to
|
||||||
|
* cornerstoneExtension, so we don't need to do anything here regarding
|
||||||
|
* the image rendering, element enabling etc.
|
||||||
|
*/
|
||||||
|
const onElementEnabled = evt => {
|
||||||
|
setElement(evt.detail.element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onElementDisabled = () => {
|
||||||
|
setElement(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const storePresentationState = useCallback(() => {
|
||||||
|
viewportGrid?.viewports.forEach(({ viewportId }) => {
|
||||||
|
commandsManager.runCommand('storePresentation', {
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [viewportGrid]);
|
||||||
|
|
||||||
|
const hydrateRTDisplaySet = useCallback(
|
||||||
|
({ rtDisplaySet, viewportId }) => {
|
||||||
|
commandsManager.runCommand('hydrateRTSDisplaySet', {
|
||||||
|
displaySet: rtDisplaySet,
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[commandsManager]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getCornerstoneViewport = useCallback(() => {
|
||||||
|
const { component: Component } = extensionManager.getModuleEntry(
|
||||||
|
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||||
|
);
|
||||||
|
|
||||||
|
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||||
|
|
||||||
|
// Todo: jump to the center of the first segment
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...props}
|
||||||
|
displaySets={[referencedDisplaySet, rtDisplaySet]}
|
||||||
|
viewportOptions={{
|
||||||
|
viewportType: 'stack',
|
||||||
|
toolGroupId: toolGroupId,
|
||||||
|
orientation: viewportOptions.orientation,
|
||||||
|
viewportId: viewportOptions.viewportId,
|
||||||
|
}}
|
||||||
|
onElementEnabled={evt => {
|
||||||
|
props.onElementEnabled?.(evt);
|
||||||
|
onElementEnabled(evt);
|
||||||
|
}}
|
||||||
|
onElementDisabled={onElementDisabled}
|
||||||
|
></Component>
|
||||||
|
);
|
||||||
|
}, [viewportId, rtDisplaySet, toolGroupId]);
|
||||||
|
|
||||||
|
const onSegmentChange = useCallback(
|
||||||
|
direction => {
|
||||||
|
const segmentationId = rtDisplaySet.displaySetInstanceUID;
|
||||||
|
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||||
|
|
||||||
|
const { segments } = segmentation;
|
||||||
|
|
||||||
|
const numberOfSegments = Object.keys(segments).length;
|
||||||
|
|
||||||
|
let newSelectedSegmentIndex = selectedSegment + direction;
|
||||||
|
|
||||||
|
// Segment 0 is always background
|
||||||
|
if (newSelectedSegmentIndex >= numberOfSegments - 1) {
|
||||||
|
newSelectedSegmentIndex = 1;
|
||||||
|
} else if (newSelectedSegmentIndex === 0) {
|
||||||
|
newSelectedSegmentIndex = numberOfSegments - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId);
|
||||||
|
setSelectedSegment(newSelectedSegmentIndex);
|
||||||
|
},
|
||||||
|
[selectedSegment, segmentationService]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rtIsLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promptHydrateRT({
|
||||||
|
servicesManager,
|
||||||
|
viewportId,
|
||||||
|
rtDisplaySet,
|
||||||
|
preHydrateCallbacks: [storePresentationState],
|
||||||
|
hydrateRTDisplaySet,
|
||||||
|
}).then(isHydrated => {
|
||||||
|
if (isHydrated) {
|
||||||
|
setIsHydrated(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// I'm not sure what is this, since in RT we support Overlapping segments
|
||||||
|
// via contours
|
||||||
|
const { unsubscribe } = segmentationService.subscribe(
|
||||||
|
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||||
|
evt => {
|
||||||
|
if (evt.rtDisplaySet.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
|
||||||
|
setRtIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt.overlappingSegments) {
|
||||||
|
uiNotificationService.show({
|
||||||
|
title: 'Overlapping Segments',
|
||||||
|
message: 'Overlapping segments detected which is not currently supported',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [rtDisplaySet]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { unsubscribe } = segmentationService.subscribe(
|
||||||
|
segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE,
|
||||||
|
({ percentComplete, numSegments }) => {
|
||||||
|
setProcessingProgress({
|
||||||
|
percentComplete,
|
||||||
|
totalSegments: numSegments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [rtDisplaySet]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Cleanup the SEG viewport when the viewport is destroyed
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||||
|
({ displaySetInstanceUIDs }) => {
|
||||||
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
|
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||||
|
viewportGridService.setDisplaySetsForViewport({
|
||||||
|
viewportId: activeViewportId,
|
||||||
|
displaySetInstanceUIDs: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
onDisplaySetsRemovedSubscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||||
|
|
||||||
|
if (toolGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||||
|
|
||||||
|
setToolGroupCreated(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// remove the segmentation representations if seg displayset changed
|
||||||
|
segmentationService.removeSegmentationRepresentations(viewportId);
|
||||||
|
|
||||||
|
toolGroupService.destroyToolGroup(toolGroupId);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsHydrated(rtDisplaySet.isHydrated);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// remove the segmentation representations if seg displayset changed
|
||||||
|
segmentationService.removeSegmentationRepresentations(viewportId);
|
||||||
|
referencedDisplaySetRef.current = null;
|
||||||
|
};
|
||||||
|
}, [rtDisplaySet]);
|
||||||
|
|
||||||
|
const onStatusClick = useCallback(async () => {
|
||||||
|
// 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,
|
||||||
|
viewportId,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsHydrated(isHydrated);
|
||||||
|
}, [hydrateRTDisplaySet, rtDisplaySet, storePresentationState, viewportId]);
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
let childrenWithProps = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!referencedDisplaySetRef.current ||
|
||||||
|
referencedDisplaySet.displaySetInstanceUID !==
|
||||||
|
referencedDisplaySetRef.current.displaySet.displaySetInstanceUID
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children && children.length) {
|
||||||
|
childrenWithProps = children.map((child, index) => {
|
||||||
|
return (
|
||||||
|
child &&
|
||||||
|
React.cloneElement(child, {
|
||||||
|
viewportId,
|
||||||
|
key: index,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
viewportActionCornersService.addComponents([
|
||||||
|
{
|
||||||
|
viewportId,
|
||||||
|
id: 'viewportStatusComponent',
|
||||||
|
component: _getStatusComponent({
|
||||||
|
isHydrated,
|
||||||
|
onStatusClick,
|
||||||
|
}),
|
||||||
|
indexPriority: -100,
|
||||||
|
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewportId,
|
||||||
|
id: 'viewportActionArrowsComponent',
|
||||||
|
component: (
|
||||||
|
<ViewportActionArrows
|
||||||
|
key="actionArrows"
|
||||||
|
onArrowsClick={onSegmentChange}
|
||||||
|
className={
|
||||||
|
viewportId === activeViewportId ? 'visible' : 'invisible group-hover/pane:visible'
|
||||||
|
}
|
||||||
|
></ViewportActionArrows>
|
||||||
|
),
|
||||||
|
indexPriority: 0,
|
||||||
|
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [
|
||||||
|
activeViewportId,
|
||||||
|
isHydrated,
|
||||||
|
onSegmentChange,
|
||||||
|
onStatusClick,
|
||||||
|
viewportActionCornersService,
|
||||||
|
viewportId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||||
|
{rtIsLoading && (
|
||||||
|
<LoadingIndicatorTotalPercent
|
||||||
|
className="h-full w-full"
|
||||||
|
totalNumbers={processingProgress.totalSegments}
|
||||||
|
percentComplete={processingProgress.percentComplete}
|
||||||
|
loadingText="Loading RTSTRUCT..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{getCornerstoneViewport()}
|
||||||
|
{childrenWithProps}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIFCornerstoneRTViewport.propTypes = {
|
||||||
|
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
viewportId: PropTypes.string.isRequired,
|
||||||
|
dataSource: PropTypes.object,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
function _getReferencedDisplaySetMetadata(referencedDisplaySet) {
|
||||||
|
const image0 = referencedDisplaySet.images[0];
|
||||||
|
const referencedDisplaySetMetadata = {
|
||||||
|
PatientID: image0.PatientID,
|
||||||
|
PatientName: image0.PatientName,
|
||||||
|
PatientSex: image0.PatientSex,
|
||||||
|
PatientAge: image0.PatientAge,
|
||||||
|
SliceThickness: image0.SliceThickness,
|
||||||
|
StudyDate: image0.StudyDate,
|
||||||
|
SeriesDescription: image0.SeriesDescription,
|
||||||
|
SeriesInstanceUID: image0.SeriesInstanceUID,
|
||||||
|
SeriesNumber: image0.SeriesNumber,
|
||||||
|
ManufacturerModelName: image0.ManufacturerModelName,
|
||||||
|
SpacingBetweenSlices: image0.SpacingBetweenSlices,
|
||||||
|
};
|
||||||
|
|
||||||
|
return referencedDisplaySetMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OHIFCornerstoneRTViewport;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Icon, Tooltip } from '@ohif/ui';
|
||||||
|
|
||||||
|
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
|
||||||
|
let ToolTipMessage = null;
|
||||||
|
let StatusIcon = null;
|
||||||
|
|
||||||
|
switch (isHydrated) {
|
||||||
|
case true:
|
||||||
|
StatusIcon = () => <Icon name="status-alert" />;
|
||||||
|
|
||||||
|
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
StatusIcon = () => (
|
||||||
|
<Icon
|
||||||
|
className="text-aqua-pale"
|
||||||
|
name="status-untracked"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusArea = () => {
|
||||||
|
const { t } = useTranslation('Common');
|
||||||
|
const loadStr = t('LOAD');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
|
||||||
|
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
|
||||||
|
<StatusIcon />
|
||||||
|
<span className="ml-1">RTSTRUCT</span>
|
||||||
|
</div>
|
||||||
|
{!isHydrated && (
|
||||||
|
<div
|
||||||
|
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
|
||||||
|
onMouseUp={onStatusClick}
|
||||||
|
>
|
||||||
|
{loadStr}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ToolTipMessage && (
|
||||||
|
<Tooltip
|
||||||
|
content={<ToolTipMessage />}
|
||||||
|
position="bottom-left"
|
||||||
|
>
|
||||||
|
<StatusArea />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{!ToolTipMessage && <StatusArea />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
extensions/cornerstone-dicom-seg/.webpack/webpack.dev.js
Normal file
12
extensions/cornerstone-dicom-seg/.webpack/webpack.dev.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||||
|
};
|
||||||
54
extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js
Normal file
54
extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const webpack = require('webpack');
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
|
const pkg = require('./../package.json');
|
||||||
|
|
||||||
|
const ROOT_DIR = path.join(__dirname, '../');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
const ENTRY = {
|
||||||
|
app: `${SRC_DIR}/index.tsx`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputName = `ohif-${pkg.name.split('/').pop()}`;
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||||
|
|
||||||
|
return merge(commonConfig, {
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: true,
|
||||||
|
timings: true,
|
||||||
|
assets: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
modules: false,
|
||||||
|
children: false,
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
sideEffects: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: ROOT_DIR,
|
||||||
|
library: 'ohif-extension-cornerstone-dicom-seg',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
filename: pkg.main,
|
||||||
|
},
|
||||||
|
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
|
maxChunks: 1,
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: `./dist/${outputName}.css`,
|
||||||
|
chunkFilename: `./dist/${outputName}.css`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
2281
extensions/cornerstone-dicom-seg/CHANGELOG.md
Normal file
2281
extensions/cornerstone-dicom-seg/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
20
extensions/cornerstone-dicom-seg/LICENSE
Normal file
20
extensions/cornerstone-dicom-seg/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Open Health Imaging Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
18
extensions/cornerstone-dicom-seg/README.md
Normal file
18
extensions/cornerstone-dicom-seg/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# dicom-seg
|
||||||
|
## Description
|
||||||
|
|
||||||
|
DICOM SEG read workflow. This extension will allow you to load a DICOM SEG image
|
||||||
|
and display it on OHIF. Currently Segmentations are loaded as a volumetric labelmap
|
||||||
|
and displayed as a 3D volume.
|
||||||
|
|
||||||
|
This extension provides a SEG viewport, which enables rendering and reviewing
|
||||||
|
of the DICOM SEG images. However, in order to fully load all the segments
|
||||||
|
you will need to click on the SEG Pill button on the viewport action bar
|
||||||
|
to fully load the segments.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
OHIF
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT
|
||||||
44
extensions/cornerstone-dicom-seg/babel.config.js
Normal file
44
extensions/cornerstone-dicom-seg/babel.config.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: 'commonjs',
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
'@babel/plugin-transform-regenerator',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
presets: [
|
||||||
|
// WebPack handles ES6 --> Target Syntax
|
||||||
|
['@babel/preset-env', { modules: false }],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
plugins: ['react-refresh/babel'],
|
||||||
|
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
54
extensions/cornerstone-dicom-seg/package.json
Normal file
54
extensions/cornerstone-dicom-seg/package.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||||
|
"version": "3.9.1",
|
||||||
|
"description": "DICOM SEG read workflow",
|
||||||
|
"author": "OHIF",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/ohif-extension-cornerstone-dicom-seg.umd.js",
|
||||||
|
"module": "src/index.tsx",
|
||||||
|
"files": [
|
||||||
|
"dist/**",
|
||||||
|
"public/**",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"repository": "OHIF/Viewers",
|
||||||
|
"keywords": [
|
||||||
|
"ohif-extension"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.18.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "shx rm -rf dist",
|
||||||
|
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||||
|
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||||
|
"dev:dicom-seg": "yarn run dev",
|
||||||
|
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||||
|
"build:package-1": "yarn run build",
|
||||||
|
"start": "yarn run dev"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ohif/core": "3.9.1",
|
||||||
|
"@ohif/extension-cornerstone": "3.9.1",
|
||||||
|
"@ohif/extension-default": "3.9.1",
|
||||||
|
"@ohif/i18n": "3.9.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-i18next": "^12.2.2",
|
||||||
|
"react-router": "^6.23.1",
|
||||||
|
"react-router-dom": "^6.23.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.13",
|
||||||
|
"@cornerstonejs/adapters": "^2.2.4",
|
||||||
|
"@cornerstonejs/core": "^2.2.4",
|
||||||
|
"@kitware/vtk.js": "32.1.0",
|
||||||
|
"react-color": "^2.19.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
379
extensions/cornerstone-dicom-seg/src/commandsModule.ts
Normal file
379
extensions/cornerstone-dicom-seg/src/commandsModule.ts
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
import { createReportDialogPrompt } from '@ohif/extension-default';
|
||||||
|
import { Types } from '@ohif/core';
|
||||||
|
import { cache, metaData } from '@cornerstonejs/core';
|
||||||
|
import {
|
||||||
|
segmentation as cornerstoneToolsSegmentation,
|
||||||
|
Enums as cornerstoneToolsEnums,
|
||||||
|
utilities,
|
||||||
|
} from '@cornerstonejs/tools';
|
||||||
|
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
|
||||||
|
import { classes, DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
const { segmentation: segmentationUtils } = utilities;
|
||||||
|
|
||||||
|
const { datasetToBlob } = dcmjs.data;
|
||||||
|
|
||||||
|
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||||
|
const { viewports, activeViewportId } = viewportGridService.getState();
|
||||||
|
const targetViewportId = viewportId || activeViewportId;
|
||||||
|
|
||||||
|
const viewport = viewports.get(targetViewportId);
|
||||||
|
|
||||||
|
return viewport;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
Cornerstone3D: {
|
||||||
|
Segmentation: { generateSegmentation },
|
||||||
|
},
|
||||||
|
} = adaptersSEG;
|
||||||
|
|
||||||
|
const {
|
||||||
|
Cornerstone3D: {
|
||||||
|
RTSS: { generateRTSSFromSegmentations },
|
||||||
|
},
|
||||||
|
} = adaptersRT;
|
||||||
|
|
||||||
|
const { downloadDICOMData } = helpers;
|
||||||
|
|
||||||
|
const commandsModule = ({
|
||||||
|
servicesManager,
|
||||||
|
extensionManager,
|
||||||
|
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
|
||||||
|
const {
|
||||||
|
segmentationService,
|
||||||
|
uiDialogService,
|
||||||
|
displaySetService,
|
||||||
|
viewportGridService,
|
||||||
|
toolGroupService,
|
||||||
|
} = servicesManager.services as AppTypes.Services;
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
/**
|
||||||
|
* 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 }) => {
|
||||||
|
// Todo: handle adding more than one segmentation
|
||||||
|
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||||
|
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
|
||||||
|
|
||||||
|
const segmentation = segmentations[0];
|
||||||
|
const segmentationId = segmentation.segmentationId;
|
||||||
|
const label = segmentation.config.label;
|
||||||
|
const segments = segmentation.config.segments;
|
||||||
|
|
||||||
|
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
|
|
||||||
|
await segmentationService.createLabelmapForDisplaySet(displaySet, {
|
||||||
|
segmentationId,
|
||||||
|
segments,
|
||||||
|
label,
|
||||||
|
});
|
||||||
|
|
||||||
|
segmentationService.addOrUpdateSegmentation(segmentation);
|
||||||
|
|
||||||
|
await segmentationService.addSegmentationRepresentation(viewport.viewportId, {
|
||||||
|
segmentationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 { imageIds } = segmentation.representationData.Labelmap;
|
||||||
|
|
||||||
|
const segImages = imageIds.map(imageId => cache.getImage(imageId));
|
||||||
|
const referencedImages = segImages.map(image => cache.getImage(image.referencedImageId));
|
||||||
|
|
||||||
|
const labelmaps2D = [];
|
||||||
|
|
||||||
|
let z = 0;
|
||||||
|
|
||||||
|
for (const segImage of segImages) {
|
||||||
|
const segmentsOnLabelmap = new Set();
|
||||||
|
const pixelData = segImage.getPixelData();
|
||||||
|
const { rows, columns } = segImage;
|
||||||
|
|
||||||
|
// Use a single pass through the pixel data
|
||||||
|
for (let i = 0; i < pixelData.length; i++) {
|
||||||
|
const segment = pixelData[i];
|
||||||
|
if (segment !== 0) {
|
||||||
|
segmentsOnLabelmap.add(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
labelmaps2D[z++] = {
|
||||||
|
segmentsOnLabelmap: Array.from(segmentsOnLabelmap),
|
||||||
|
pixelData,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSegmentsOnLabelmap = labelmaps2D.map(labelmap => labelmap.segmentsOnLabelmap);
|
||||||
|
|
||||||
|
const labelmap3D = {
|
||||||
|
segmentsOnLabelmap: Array.from(new Set(allSegmentsOnLabelmap.flat())),
|
||||||
|
metadata: [],
|
||||||
|
labelmaps2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
|
||||||
|
const representations = segmentationService.getRepresentationsForSegmentation(segmentationId);
|
||||||
|
|
||||||
|
Object.entries(segmentationInOHIF.segments).forEach(([segmentIndex, segment]) => {
|
||||||
|
// segmentation service already has a color for each segment
|
||||||
|
if (!segment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { label } = segment;
|
||||||
|
|
||||||
|
const firstRepresentation = representations[0];
|
||||||
|
const color = segmentationService.getSegmentColor(
|
||||||
|
firstRepresentation.viewportId,
|
||||||
|
segmentationId,
|
||||||
|
segment.segmentIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
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: segment?.algorithmType || 'MANUAL',
|
||||||
|
SegmentAlgorithmName: segment?.algorithmName || 'OHIF Brush',
|
||||||
|
RecommendedDisplayCIELabValue,
|
||||||
|
SegmentedPropertyCategoryCodeSequence: {
|
||||||
|
CodeValue: 'T-D0050',
|
||||||
|
CodingSchemeDesignator: 'SRT',
|
||||||
|
CodeMeaning: 'Tissue',
|
||||||
|
},
|
||||||
|
SegmentedPropertyTypeCodeSequence: {
|
||||||
|
CodeValue: 'T-D0050',
|
||||||
|
CodingSchemeDesignator: 'SRT',
|
||||||
|
CodeMeaning: 'Tissue',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
labelmap3D.metadata[segmentIndex] = segmentMetadata;
|
||||||
|
});
|
||||||
|
|
||||||
|
const generatedSegmentation = generateSegmentation(
|
||||||
|
referencedImages,
|
||||||
|
labelmap3D,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBrushSize: ({ value, toolNames }) => {
|
||||||
|
const brushSize = Number(value);
|
||||||
|
|
||||||
|
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||||
|
if (toolNames?.length === 0) {
|
||||||
|
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize);
|
||||||
|
} else {
|
||||||
|
toolNames?.forEach(toolName => {
|
||||||
|
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setThresholdRange: ({
|
||||||
|
value,
|
||||||
|
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
|
||||||
|
}) => {
|
||||||
|
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
|
||||||
|
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||||
|
toolNames?.forEach(toolName => {
|
||||||
|
toolGroup.setToolConfiguration(toolName, {
|
||||||
|
strategySpecificConfiguration: {
|
||||||
|
THRESHOLD: {
|
||||||
|
threshold: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const definitions = {
|
||||||
|
/**
|
||||||
|
* Obsolete?
|
||||||
|
*/
|
||||||
|
loadSegmentationDisplaySetsForViewport: {
|
||||||
|
commandFn: actions.loadSegmentationDisplaySetsForViewport,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Obsolete?
|
||||||
|
*/
|
||||||
|
loadSegmentationsForViewport: {
|
||||||
|
commandFn: actions.loadSegmentationsForViewport,
|
||||||
|
},
|
||||||
|
|
||||||
|
generateSegmentation: {
|
||||||
|
commandFn: actions.generateSegmentation,
|
||||||
|
},
|
||||||
|
downloadSegmentation: {
|
||||||
|
commandFn: actions.downloadSegmentation,
|
||||||
|
},
|
||||||
|
storeSegmentation: {
|
||||||
|
commandFn: actions.storeSegmentation,
|
||||||
|
},
|
||||||
|
downloadRTSS: {
|
||||||
|
commandFn: actions.downloadRTSS,
|
||||||
|
},
|
||||||
|
setBrushSize: {
|
||||||
|
commandFn: actions.setBrushSize,
|
||||||
|
},
|
||||||
|
setThresholdRange: {
|
||||||
|
commandFn: actions.setThresholdRange,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
definitions,
|
||||||
|
defaultContext: 'SEGMENTATION',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default commandsModule;
|
||||||
101
extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts
Normal file
101
extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { Types } from '@ohif/core';
|
||||||
|
|
||||||
|
const segProtocol: Types.HangingProtocol.Protocol = {
|
||||||
|
id: '@ohif/seg',
|
||||||
|
// Don't store this hanging protocol as it applies to the currently active
|
||||||
|
// display set by default
|
||||||
|
// cacheId: null,
|
||||||
|
name: 'Segmentations',
|
||||||
|
// Just apply this one when specifically listed
|
||||||
|
protocolMatchingRules: [],
|
||||||
|
toolGroupIds: ['default'],
|
||||||
|
// -1 would be used to indicate active only, whereas other values are
|
||||||
|
// the number of required priors referenced - so 0 means active with
|
||||||
|
// 0 or more priors.
|
||||||
|
numberOfPriorsReferenced: 0,
|
||||||
|
// Default viewport is used to define the viewport when
|
||||||
|
// additional viewports are added using the layout tool
|
||||||
|
defaultViewport: {
|
||||||
|
viewportOptions: {
|
||||||
|
viewportType: 'stack',
|
||||||
|
toolGroupId: 'default',
|
||||||
|
allowUnmatchedView: true,
|
||||||
|
syncGroups: [
|
||||||
|
{
|
||||||
|
type: 'hydrateseg',
|
||||||
|
id: 'sameFORId',
|
||||||
|
source: true,
|
||||||
|
target: true,
|
||||||
|
// options: {
|
||||||
|
// matchingRules: ['sameFOR'],
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
id: 'segDisplaySetId',
|
||||||
|
matchedDisplaySetsIndex: -1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
displaySetSelectors: {
|
||||||
|
segDisplaySetId: {
|
||||||
|
seriesMatchingRules: [
|
||||||
|
{
|
||||||
|
attribute: 'Modality',
|
||||||
|
constraint: {
|
||||||
|
equals: 'SEG',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
name: 'Segmentations',
|
||||||
|
viewportStructure: {
|
||||||
|
layoutType: 'grid',
|
||||||
|
properties: {
|
||||||
|
rows: 1,
|
||||||
|
columns: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
viewports: [
|
||||||
|
{
|
||||||
|
viewportOptions: {
|
||||||
|
allowUnmatchedView: true,
|
||||||
|
syncGroups: [
|
||||||
|
{
|
||||||
|
type: 'hydrateseg',
|
||||||
|
id: 'sameFORId',
|
||||||
|
source: true,
|
||||||
|
target: true,
|
||||||
|
// options: {
|
||||||
|
// matchingRules: ['sameFOR'],
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
id: 'segDisplaySetId',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function getHangingProtocolModule() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: segProtocol.id,
|
||||||
|
protocol: segProtocol,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getHangingProtocolModule;
|
||||||
|
export { segProtocol };
|
||||||
255
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
Normal file
255
extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import { utils } from '@ohif/core';
|
||||||
|
import { metaData, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||||
|
import { CONSTANTS, segmentation as cstSegmentation } from '@cornerstonejs/tools';
|
||||||
|
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
|
||||||
|
|
||||||
|
import { SOPClassHandlerId } from './id';
|
||||||
|
import { dicomlabToRGB } from './utils/dicomlabToRGB';
|
||||||
|
|
||||||
|
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4'];
|
||||||
|
|
||||||
|
const loadPromises = {};
|
||||||
|
|
||||||
|
function _getDisplaySetsFromSeries(
|
||||||
|
instances,
|
||||||
|
servicesManager: AppTypes.ServicesManager,
|
||||||
|
extensionManager
|
||||||
|
) {
|
||||||
|
const instance = instances[0];
|
||||||
|
|
||||||
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPClassUID,
|
||||||
|
wadoRoot,
|
||||||
|
wadoUri,
|
||||||
|
wadoUriRoot,
|
||||||
|
} = instance;
|
||||||
|
|
||||||
|
const displaySet = {
|
||||||
|
Modality: 'SEG',
|
||||||
|
loading: false,
|
||||||
|
isReconstructable: true, // by default for now since it is a volumetric SEG currently
|
||||||
|
displaySetInstanceUID: utils.guid(),
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPClassHandlerId,
|
||||||
|
SOPClassUID,
|
||||||
|
referencedImages: null,
|
||||||
|
referencedSeriesInstanceUID: null,
|
||||||
|
referencedDisplaySetInstanceUID: null,
|
||||||
|
isDerivedDisplaySet: true,
|
||||||
|
isLoaded: false,
|
||||||
|
isHydrated: false,
|
||||||
|
segments: {},
|
||||||
|
sopClassUids,
|
||||||
|
instance,
|
||||||
|
instances: [instance],
|
||||||
|
wadoRoot,
|
||||||
|
wadoUriRoot,
|
||||||
|
wadoUri,
|
||||||
|
isOverlayDisplaySet: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||||
|
|
||||||
|
if (!referencedSeriesSequence) {
|
||||||
|
console.error('ReferencedSeriesSequence is missing for the SEG');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence;
|
||||||
|
|
||||||
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||||
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||||
|
const { displaySetService } = servicesManager.services;
|
||||||
|
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||||
|
displaySet.referencedSeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const referencedDisplaySet = referencedDisplaySets[0];
|
||||||
|
|
||||||
|
if (!referencedDisplaySet) {
|
||||||
|
// subscribe to display sets added which means at some point it will be available
|
||||||
|
const { unsubscribe } = displaySetService.subscribe(
|
||||||
|
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||||
|
({ displaySetsAdded }) => {
|
||||||
|
// here we can also do a little bit of search, since sometimes DICOM SEG
|
||||||
|
// does not contain the referenced display set uid , and we can just
|
||||||
|
// see which of the display sets added is more similar and assign it
|
||||||
|
// to the referencedDisplaySet
|
||||||
|
const addedDisplaySet = displaySetsAdded[0];
|
||||||
|
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||||
|
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySet.load = async ({ headers }) =>
|
||||||
|
await _load(displaySet, servicesManager, extensionManager, headers);
|
||||||
|
|
||||||
|
return [displaySet];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _load(
|
||||||
|
segDisplaySet,
|
||||||
|
servicesManager: AppTypes.ServicesManager,
|
||||||
|
extensionManager,
|
||||||
|
headers
|
||||||
|
) {
|
||||||
|
const { SOPInstanceUID } = segDisplaySet;
|
||||||
|
const { segmentationService } = servicesManager.services;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(segDisplaySet.loading || segDisplaySet.isLoaded) &&
|
||||||
|
loadPromises[SOPInstanceUID] &&
|
||||||
|
_segmentationExists(segDisplaySet)
|
||||||
|
) {
|
||||||
|
return loadPromises[SOPInstanceUID];
|
||||||
|
}
|
||||||
|
|
||||||
|
segDisplaySet.loading = true;
|
||||||
|
|
||||||
|
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
||||||
|
// and also return the same promise to any other callers.
|
||||||
|
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
||||||
|
if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) {
|
||||||
|
try {
|
||||||
|
await _loadSegments({
|
||||||
|
extensionManager,
|
||||||
|
servicesManager,
|
||||||
|
segDisplaySet,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
segDisplaySet.loading = false;
|
||||||
|
return reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentationService
|
||||||
|
.createSegmentationForSEGDisplaySet(segDisplaySet)
|
||||||
|
.then(() => {
|
||||||
|
segDisplaySet.loading = false;
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
segDisplaySet.loading = false;
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return loadPromises[SOPInstanceUID];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _loadSegments({
|
||||||
|
extensionManager,
|
||||||
|
servicesManager,
|
||||||
|
segDisplaySet,
|
||||||
|
headers,
|
||||||
|
}: withAppTypes) {
|
||||||
|
const utilityModule = extensionManager.getModuleEntry(
|
||||||
|
'@ohif/extension-cornerstone.utilityModule.common'
|
||||||
|
);
|
||||||
|
|
||||||
|
const { segmentationService, uiNotificationService } = servicesManager.services;
|
||||||
|
|
||||||
|
const { dicomLoaderService } = utilityModule.exports;
|
||||||
|
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);
|
||||||
|
|
||||||
|
const referencedDisplaySet = servicesManager.services.displaySetService.getDisplaySetByUID(
|
||||||
|
segDisplaySet.referencedDisplaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!referencedDisplaySet) {
|
||||||
|
throw new Error('referencedDisplaySet is missing for SEG');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { instances: images } = referencedDisplaySet;
|
||||||
|
const imageIds = images.map(({ imageId }) => imageId);
|
||||||
|
|
||||||
|
// 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 }
|
||||||
|
);
|
||||||
|
|
||||||
|
let usedRecommendedDisplayCIELabValue = true;
|
||||||
|
results.segMetadata.data.forEach((data, i) => {
|
||||||
|
if (i > 0) {
|
||||||
|
data.rgba = data.RecommendedDisplayCIELabValue;
|
||||||
|
|
||||||
|
if (data.rgba) {
|
||||||
|
data.rgba = dicomlabToRGB(data.rgba);
|
||||||
|
} else {
|
||||||
|
usedRecommendedDisplayCIELabValue = false;
|
||||||
|
data.rgba = CONSTANTS.COLOR_LUT[i % CONSTANTS.COLOR_LUT.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.overlappingSegments) {
|
||||||
|
uiNotificationService.show({
|
||||||
|
title: 'Overlapping Segments',
|
||||||
|
message:
|
||||||
|
'Unsupported overlapping segments detected, segmentation rendering results may be incorrect.',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usedRecommendedDisplayCIELabValue) {
|
||||||
|
// Display a notification about the non-utilization of RecommendedDisplayCIELabValue
|
||||||
|
uiNotificationService.show({
|
||||||
|
title: 'DICOM SEG import',
|
||||||
|
message:
|
||||||
|
'RecommendedDisplayCIELabValue not found for one or more segments. The default color was used instead.',
|
||||||
|
type: 'warning',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(segDisplaySet, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _segmentationExists(segDisplaySet) {
|
||||||
|
return cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||||
|
const getDisplaySetsFromSeries = instances => {
|
||||||
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'dicom-seg',
|
||||||
|
sopClassUids,
|
||||||
|
getDisplaySetsFromSeries,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSopClassHandlerModule;
|
||||||
57
extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
Normal file
57
extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||||
|
const { segmentationService, toolbarService, toolGroupService } = servicesManager.services;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'evaluate.cornerstone.segmentation',
|
||||||
|
evaluate: ({ viewportId, button, toolNames, disabledText }) => {
|
||||||
|
// Todo: we need to pass in the button section Id since we are kind of
|
||||||
|
// forcing the button to have black background since initially
|
||||||
|
// it is designed for the toolbox not the toolbar on top
|
||||||
|
// we should then branch the buttonSectionId to have different styles
|
||||||
|
const segmentations = segmentationService.getSegmentationRepresentations(viewportId);
|
||||||
|
if (!segmentations?.length) {
|
||||||
|
return {
|
||||||
|
disabled: true,
|
||||||
|
className: '!text-common-bright !bg-black opacity-50',
|
||||||
|
disabledText: disabledText ?? 'No segmentations available',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||||
|
|
||||||
|
if (!toolGroup) {
|
||||||
|
return {
|
||||||
|
disabled: true,
|
||||||
|
className: '!text-common-bright ohif-disabled',
|
||||||
|
disabledText: disabledText ?? 'Not available on the current viewport',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolName = toolbarService.getToolNameForButton(button);
|
||||||
|
|
||||||
|
if (!toolGroup.hasTool(toolName) && !toolNames) {
|
||||||
|
return {
|
||||||
|
disabled: true,
|
||||||
|
className: '!text-common-bright ohif-disabled',
|
||||||
|
disabledText: disabledText ?? 'Not available on the current viewport',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPrimaryActive = toolNames
|
||||||
|
? toolNames.includes(toolGroup.getActivePrimaryMouseButtonTool())
|
||||||
|
: toolGroup.getActivePrimaryMouseButtonTool() === toolName;
|
||||||
|
|
||||||
|
return {
|
||||||
|
disabled: false,
|
||||||
|
className: isPrimaryActive
|
||||||
|
? '!text-black !bg-primary-light hover:bg-primary-light hover-text-black hover:cursor-pointer'
|
||||||
|
: '!text-common-bright !bg-black hover:bg-primary-light hover:cursor-pointer hover:text-black',
|
||||||
|
// Todo: isActive right now is used for nested buttons where the primary
|
||||||
|
// button needs to be fully rounded (vs partial rounded) when active
|
||||||
|
// otherwise it does not have any other use
|
||||||
|
isActive: isPrimaryActive,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
7
extensions/cornerstone-dicom-seg/src/id.js
Normal file
7
extensions/cornerstone-dicom-seg/src/id.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
|
const id = packageJson.name;
|
||||||
|
const SOPClassHandlerName = 'dicom-seg';
|
||||||
|
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||||
|
|
||||||
|
export { id, SOPClassHandlerId, SOPClassHandlerName };
|
||||||
56
extensions/cornerstone-dicom-seg/src/index.tsx
Normal file
56
extensions/cornerstone-dicom-seg/src/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { id } from './id';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
|
import getHangingProtocolModule from './getHangingProtocolModule';
|
||||||
|
import getCommandsModule from './commandsModule';
|
||||||
|
import { getToolbarModule } from './getToolbarModule';
|
||||||
|
|
||||||
|
const Component = React.lazy(() => {
|
||||||
|
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const OHIFCornerstoneSEGViewport = props => {
|
||||||
|
return (
|
||||||
|
<React.Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Component {...props} />
|
||||||
|
</React.Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can remove any of the following modules if you don't need them.
|
||||||
|
*/
|
||||||
|
const extension = {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
* You ID can be anything you want, but it should be unique.
|
||||||
|
*/
|
||||||
|
id,
|
||||||
|
getCommandsModule,
|
||||||
|
getToolbarModule,
|
||||||
|
getViewportModule({ servicesManager, extensionManager, commandsManager }) {
|
||||||
|
const ExtendedOHIFCornerstoneSEGViewport = props => {
|
||||||
|
return (
|
||||||
|
<OHIFCornerstoneSEGViewport
|
||||||
|
servicesManager={servicesManager}
|
||||||
|
extensionManager={extensionManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
||||||
|
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
||||||
|
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
||||||
|
* Examples include the default sop class handler provided by the default extension
|
||||||
|
*/
|
||||||
|
getSopClassHandlerModule,
|
||||||
|
getHangingProtocolModule,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default extension;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum SegmentationPanelMode {
|
||||||
|
Expanded = 'expanded',
|
||||||
|
Dropdown = 'dropdown',
|
||||||
|
}
|
||||||
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 };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
|
||||||
|
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
|
||||||
|
|
||||||
|
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createSEGToolGroupAndAddTools;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user