diff --git a/.circleci/config.yml b/.circleci/config.yml index 9aa3bc95c6a..d2fce6395a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,22 +13,24 @@ version: 2.1 ## orbs: codecov: codecov/codecov@1.0.5 - cypress: cypress-io/cypress@1.26.0 + cypress: cypress-io/cypress@3.1.4 + executors: - # Custom executor to override Cypress config - deploy-to-prod-executor: - docker: - - image: cimg/node:16.14 - environment: - CYPRESS_BASE_URL: https://ohif-staging.netlify.com/ - chrome-and-pacs: + cypress-custom: + description: | + Single Docker container used to run Cypress Tests docker: - # Primary container image where all steps run. - - image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102' + - image: cimg/node:<< parameters.node-version >>-browsers + parameters: + node-version: + default: '18.16.1' + description: | + The version of Node to run your tests with. + type: string defaults: &defaults docker: - - image: cimg/node:16.14-browsers + - image: cimg/node:18.18 environment: TERM: xterm # Enable colors in term QUICK_BUILD: true @@ -43,6 +45,7 @@ jobs: steps: # Update yarn - run: yarn -v + - run: node --version # Checkout code and ALL Git Tags - checkout - restore_cache: @@ -90,43 +93,6 @@ jobs: file: '/home/circleci/repo/platform/core/coverage/reports' flags: 'core' - ### - # Workflow: PR_OPTIONAL_DOCKER_PUBLISH - ### - DOCKER_PR_PUBLISH: - <<: *defaults - steps: - # Enable yarn workspaces - - run: yarn config set workspaces-experimental true - - # Checkout code and ALL Git Tags - - checkout - - restore_cache: - name: Restore Yarn and Cypress Package Cache - keys: - # when lock file changes, use increasingly general patterns to restore cache - - yarn-packages-{{ checksum "yarn.lock" }} - - yarn-packages- - - - run: - name: Install Dependencies - command: yarn install --frozen-lockfile - - - setup_remote_docker: - docker_layer_caching: false - - - run: - name: Build and push Docker image - command: | - # Remove npm config - rm -f ./.npmrc - # Set our version number using vars - echo $CIRCLE_BUILD_NUM - # Build our image, auth, and push - docker build --tag ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM . - echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin - docker push ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM - ### # Workflow: DEPLOY ### @@ -177,62 +143,52 @@ jobs: - commit.txt - version.json - # DEPLOY_TO_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_STAGING: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_PRODUCTION: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_RELEASE_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 3270878-22 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: - # cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy + # just to make sure later on we can publish them + BUILD_PACKAGES_QUICK: + <<: *defaults + steps: + - run: yarn -v + # Checkout code and ALL Git Tags + - checkout + - attach_workspace: + at: ~/repo + # Use increasingly general patterns to restore cache + - restore_cache: + name: Restore Yarn and Cypress Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - yarn-packages- + - run: + name: Install Dependencies + command: yarn install --frozen-lockfile + - save_cache: + name: Save Yarn Package Cache + paths: + - ~/.cache/yarn + key: yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Avoid hosts unknown for github + command: | + rm -rf ~/.ssh + mkdir ~/.ssh/ + echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config + git config --global user.email "danny.ri.brown+ohif-bot@gmail.com" + git config --global user.name "ohif-bot" + - run: + name: Authenticate with NPM registry + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + - run: + name: Increase the event emitter limit + command: | + node ./increaseEventEmitterLimit.mjs + - run: + name: build half of the packages (to avoid out of memory in circleci) + command: | + yarn run build:package-all + - run: + name: build the other half of the packages + command: | + yarn run build:package-all-1 ### # Workflow: RELEASE @@ -269,8 +225,7 @@ jobs: git config --global user.name "ohif-bot" - run: name: Authenticate with NPM registry - command: - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: Increase the event emitter limit command: | @@ -283,14 +238,21 @@ jobs: name: build the other half of the packages command: | yarn run build:package-all-1 + - run: + name: increase min time out + command: | + npm config set fetch-retry-mintimeout 20000 + - run: + name: increase max time out + command: | + npm config set fetch-retry-maxtimeout 120000 - run: name: publish package versions command: | 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 + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: publish package dist command: | @@ -337,8 +299,7 @@ jobs: - setup_remote_docker: docker_layer_caching: false - run: - name: - Build and push Docker image from the master branch (beta releases) + name: Build and push Docker image from the master branch (beta releases) command: | echo $(ls -l) @@ -365,71 +326,152 @@ jobs: docker push ohif/app:$IMAGE_VERSION_FULL fi -workflows: - version: 2 + # This is copied from the Cypress orb since the default for cypress/run is node 16 and + # we migrated to 18 + CYPRESS_CUSTOM_RUN: + description: | + A single, complete job to run Cypress end-to-end tests in your application. + executor: cypress-custom + parallelism: << parameters.parallelism >> + parameters: + cypress-cache-key: + default: cypress-cache-{{ arch }}-{{ checksum "package.json" }} + description: Cache key used to cache the Cypress binary. + type: string + cypress-cache-path: + default: ~/.cache/Cypress + description: | + By default, this will cache the '~/.cache/Cypress' directory so that the Cypress binary is cached. You can override this by providing your own cache path. + type: string + cypress-command: + default: npx cypress run + description: Command used to run your Cypress tests + type: string + include-branch-in-node-cache-key: + default: false + description: | + If true, this cache will only apply to runs within the same branch. (Adds -{{ .Branch }}- to the node cache key) + type: boolean + install-browsers: + default: false + description: | + Cypress runs by default in the Electron browser. Use this flag to install additional browsers to run your tests in. + This is only needed if you are passing the `--browser` flag in your `cypress-command`. + This parameter leverages the `circleci/browser-tools` orb and includes Chrome and FireFox. + If you need additional browser support you can set this to false and use an executor with a docker image + that includes the browsers of your choosing. See https://hub.docker.com/r/cypress/browsers/tags + type: boolean + install-command: + default: '' + description: Overrides the default NPM command (npm ci) + type: string + node-cache-version: + default: v1 + description: + Change the default node cache version if you need to clear the cache for any reason. + type: string + package-manager: + default: npm + description: Select the default node package manager to use. NPM v5+ Required. + enum: + - npm + - yarn + - yarn-berry + type: enum + parallelism: + default: 1 + description: | + Number of Circle machines to use for load balancing, min 1 + (requires `parallel` and `record` flags in your `cypress-command`) + type: integer + post-install: + default: '' + description: | + Additional commands to run after running install but before verifying Cypress and saving cache. + type: string + start-command: + default: '' + description: Command used to start your local dev server for Cypress to tests against + type: string + working-directory: + default: '' + description: Directory containing package.json + type: string + steps: + - cypress/install: + cypress-cache-key: << parameters.cypress-cache-key >> + cypress-cache-path: << parameters.cypress-cache-path >> + include-branch-in-node-cache-key: << parameters.include-branch-in-node-cache-key >> + install-browsers: << parameters.install-browsers >> + install-command: << parameters.install-command >> + node-cache-version: << parameters.node-cache-version >> + package-manager: << parameters.package-manager >> + post-install: << parameters.post-install >> + working-directory: << parameters.working-directory >> + - cypress/run-tests: + cypress-command: << parameters.cypress-command >> + start-command: << parameters.start-command >> + working-directory: << parameters.working-directory >> +workflows: PR_CHECKS: jobs: - - UNIT_TESTS - - # E2E: PWA - - cypress/run: - name: 'E2E: PWA' - executor: chrome-and-pacs - browser: chrome - pre-steps: - - run: | - # Clear yarn cache; use yarn from image (update image to update yarn) - rm -rf ~/.yarn - yarn -v - yarn: true - record: true - store_artifacts: true - working_directory: platform/app - build: yarn test:data - start: yarn run test:e2e:serve - spec: 'cypress/integration/**/*' - wait-on: 'http://localhost:3000' - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos - - store_test_results: - path: platform/app/cypress/results + - BUILD_PACKAGES_QUICK: + filters: + branches: + ignore: master + - UNIT_TESTS: requires: - - UNIT_TESTS - - PR_OPTIONAL_VISUAL_TESTS: - jobs: - - AWAIT_APPROVAL: - type: approval - # Update hub.docker.org - - cypress/run: - name: 'Generate Percy Snapshots' - executor: cypress/browsers-chrome76 - browser: chrome - pre-steps: - - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' - yarn: true - store_artifacts: false - working_directory: platform/app - build: - yarn test:data && npx cross-env QUICK_BUILD=true - APP_CONFIG=config/dicomweb-server.js yarn run build - # start server --> verify running --> percy + chrome + cypress - command: yarn run test:e2e:dist - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos + - BUILD_PACKAGES_QUICK + - CYPRESS_CUSTOM_RUN: + name: 'Cypress Tests' + context: cypress + matrix: + parameters: + start-command: + - yarn run test:data && yarn run test:e2e:serve + install-browsers: + - true + cypress-command: + - 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run + --record --browser chrome --parallel' + package-manager: + - 'yarn' + cypress-cache-key: + - 'yarn-packages-{{ checksum "yarn.lock" }}' + cypress-cache-path: + - '~/.cache/Cypress' requires: - - AWAIT_APPROVAL + - BUILD_PACKAGES_QUICK + + # PR_OPTIONAL_VISUAL_TESTS: + # jobs: + # - AWAIT_APPROVAL: + # type: approval + # # Update hub.docker.org + # - cypress/run: + # name: 'Generate Percy Snapshots' + # executor: cypress/browsers-chrome76 + # browser: chrome + # pre-steps: + # - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' + # yarn: true + # store_artifacts: false + # working_directory: platform/app + # build: + # yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js + # yarn run build + # # start server --> verify running --> percy + chrome + cypress + # command: yarn run test:e2e:dist + # cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' + # no-workspace: true # Don't persist workspace + # post-steps: + # - store_artifacts: + # path: platform/app/cypress/screenshots + # - store_artifacts: + # path: platform/app/cypress/videos + # requires: + # - AWAIT_APPROVAL # Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is # deployed from the release branch which is more stable and less frequently updated. diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 00000000000..ab57ad157a0 --- /dev/null +++ b/.codespellrc @@ -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 diff --git a/.docker/Nginx-Orthanc/config/nginx.conf b/.docker/Nginx-Orthanc/config/nginx.conf deleted file mode 100644 index c38ee5813df..00000000000 --- a/.docker/Nginx-Orthanc/config/nginx.conf +++ /dev/null @@ -1,48 +0,0 @@ -worker_processes 1; - -events { worker_connections 1024; } - -http { - - upstream orthanc-server { - server orthanc:8042; - } - - server { - listen [::]:80 default_server; - listen 80; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - - location / { - - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - proxy_pass http://orthanc:8042; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $server_name; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - } - } -} diff --git a/.docker/Nginx-Orthanc/config/orthanc.json b/.docker/Nginx-Orthanc/config/orthanc.json deleted file mode 100644 index 2e10723c049..00000000000 --- a/.docker/Nginx-Orthanc/config/orthanc.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "Name": "Orthanc inside Docker", - "StorageDirectory": "/var/lib/orthanc/db", - "IndexDirectory": "/var/lib/orthanc/db", - "StorageCompression": false, - "MaximumStorageSize": 0, - "MaximumPatientCount": 0, - "LuaScripts": [], - "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"], - "ConcurrentJobs": 2, - "HttpServerEnabled": true, - "HttpPort": 8042, - "HttpDescribeErrors": true, - "HttpCompressionEnabled": true, - "DicomServerEnabled": true, - "DicomAet": "ORTHANC", - "DicomCheckCalledAet": false, - "DicomPort": 4242, - "DefaultEncoding": "Latin1", - "DeflatedTransferSyntaxAccepted": true, - "JpegTransferSyntaxAccepted": true, - "Jpeg2000TransferSyntaxAccepted": true, - "JpegLosslessTransferSyntaxAccepted": true, - "JpipTransferSyntaxAccepted": true, - "Mpeg2TransferSyntaxAccepted": true, - "RleTransferSyntaxAccepted": true, - "UnknownSopClassAccepted": false, - "DicomScpTimeout": 30, - - "RemoteAccessAllowed": true, - "SslEnabled": false, - "SslCertificate": "certificate.pem", - "AuthenticationEnabled": false, - "RegisteredUsers": { - "test": "test" - }, - "DicomModalities": {}, - "DicomModalitiesInDatabase": false, - "DicomAlwaysAllowEcho": true, - "DicomAlwaysAllowStore": true, - "DicomCheckModalityHost": false, - "DicomScuTimeout": 10, - "OrthancPeers": {}, - "OrthancPeersInDatabase": false, - "HttpProxy": "", - - "HttpVerbose": true, - - "HttpTimeout": 10, - "HttpsVerifyPeers": true, - "HttpsCACertificates": "", - "UserMetadata": {}, - "UserContentType": {}, - "StableAge": 60, - "StrictAetComparison": false, - "StoreMD5ForAttachments": true, - "LimitFindResults": 0, - "LimitFindInstances": 0, - "LimitJobs": 10, - "LogExportedResources": false, - "KeepAlive": true, - "TcpNoDelay": true, - "HttpThreadsCount": 50, - "StoreDicom": true, - "DicomAssociationCloseDelay": 5, - "QueryRetrieveSize": 10, - "CaseSensitivePN": false, - "LoadPrivateDictionary": true, - "Dictionary": {}, - "SynchronousCMove": true, - "JobsHistorySize": 10, - "SaveJobs": true, - "OverwriteInstances": false, - "MediaArchiveSize": 1, - "StorageAccessOnFind": "Always", - "MetricsEnabled": true, - - "DicomWeb": { - "Enable": true, - "Root": "/dicom-web/", - "EnableWado": true, - "WadoRoot": "/wado", - "Host": "127.0.0.1", - "Ssl": false, - "StowMaxInstances": 10, - "StowMaxSize": 10, - "QidoCaseSensitive": false - } -} diff --git a/.docker/Nginx-Orthanc/docker-compose.yml b/.docker/Nginx-Orthanc/docker-compose.yml deleted file mode 100644 index eba7911a316..00000000000 --- a/.docker/Nginx-Orthanc/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.5' - -services: - orthanc: - image: jodogne/orthanc-plugins:1.11.0 - hostname: orthanc - volumes: - # Config - - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro - # Persist data - - ./volumes/orthanc-db/:/var/lib/orthanc/db/ - ports: - - '4242:4242' # DICOM - - '8042:8042' # Web - restart: unless-stopped - nginx: - image: nginx:latest - volumes: - - ./config/nginx.conf:/etc/nginx/nginx.conf - ports: - - '80:80' #ngnix proxy - depends_on: - - orthanc diff --git a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore b/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore deleted file mode 100644 index d6b7ef32c84..00000000000 --- a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/.docker/Viewer-v3.x/default.conf.template b/.docker/Viewer-v3.x/default.conf.template index 91776166ff1..153ea708620 100644 --- a/.docker/Viewer-v3.x/default.conf.template +++ b/.docker/Viewer-v3.x/default.conf.template @@ -1,5 +1,6 @@ server { - listen ${PORT}; + listen ${PORT} default_server; + listen [::]:${PORT} default_server; location / { root /usr/share/nginx/html; index index.html index.htm; diff --git a/.docker/Viewer-v3.x/default.ssl.conf.template b/.docker/Viewer-v3.x/default.ssl.conf.template new file mode 100644 index 00000000000..06ed4e505dd --- /dev/null +++ b/.docker/Viewer-v3.x/default.ssl.conf.template @@ -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; + } +} diff --git a/.docker/Viewer-v3.x/entrypoint.sh b/.docker/Viewer-v3.x/entrypoint.sh index 400226413f6..988e087e83e 100644 --- a/.docker/Viewer-v3.x/entrypoint.sh +++ b/.docker/Viewer-v3.x/entrypoint.sh @@ -1,6 +1,16 @@ #!/bin/sh -envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf +if [ -n "$SSL_PORT" ] + then + envsubst '${SSL_PORT}:${PORT}' < /usr/src/default.ssl.conf.template > /etc/nginx/conf.d/default.conf + else + envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf +fi + +if [ -n "$APP_CONFIG" ] + then + echo "$APP_CONFIG" > /usr/share/nginx/html/app-config.js +fi if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ] then diff --git a/.dockerignore b/.dockerignore index d4a539c50d9..ca74c539884 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,12 @@ # 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/ @@ -28,3 +34,4 @@ dockerfile .vscode/ coverage/ docs/ +testdata/ diff --git a/.eslintrc.json b/.eslintrc.json index 65affdc01d5..bc37a7dcf4d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,5 @@ { - "plugins": [ - "@typescript-eslint", - "import", - "eslint-plugin-tsdoc", - "prettier" - ], + "plugins": ["@typescript-eslint", "import", "eslint-plugin-tsdoc", "prettier"], "extends": [ "react-app", "eslint:recommended", @@ -21,6 +16,10 @@ "version": "detect" } }, + "rules": { + // Enforce consistent brace style for all control statements for readability + "curly": "error" + }, "globals": { "cy": true, "before": true, diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 5a2907b18fd..83577dd7c13 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -39,8 +39,7 @@ body: attributes: label: The current behavior description: - 'A clear and concise description of what happens instead of the expected - behavior.' + 'A clear and concise description of what happens instead of the expected behavior.' validations: required: true @@ -48,8 +47,7 @@ body: id: expected_behavior attributes: label: The expected behavior - description: - 'A clear and concise description of what you expected to happen.' + description: 'A clear and concise description of what you expected to happen.' validations: required: true @@ -66,7 +64,7 @@ body: attributes: label: 'Node version' description: 'Your Node.js version.' - placeholder: 'e.g., 16.14.0' + placeholder: 'e.g., 18.16.1' validations: required: true - type: input @@ -81,6 +79,6 @@ body: - 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. + > :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. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index e03e8d9afe1..0331ba9bfa5 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -20,17 +20,15 @@ body: 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.' + '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.' + description: 'Discuss if and how the requested feature interacts with existing features.' placeholder: '...' validations: required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cc3752267f1..6a1753d87d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -83,7 +83,7 @@ after the commits are squashed. #### Tested Environment - [] OS: -- [] Node version: +- [] Node version: - [] Browser: diff --git a/.github/stale.yml b/.github/stale.yml index 4d6cdd4e356..28535cca620 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -19,8 +19,7 @@ exemptLabels: 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. + 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 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 00000000000..7373affc383 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,22 @@ +--- +name: Codespell + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Codespell + uses: codespell-project/actions-codespell@v2 diff --git a/.gitignore b/.gitignore index 2911f13930f..ed3400749a7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ platform/app/src/pluginImports.js /Viewers.iml platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/* platform/app/.recipes/OpenResty-Orthanc/logs/* +.vercel + +.vs diff --git a/.gitmodules b/.gitmodules index 413cf77c17f..f787a67a4b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "testdata"] path = testdata url = https://github.com/OHIF/viewer-testdata-dicomweb.git + branch = main diff --git a/.netlify/www/index.html b/.netlify/www/index.html index 3d889be8291..c817960c50d 100644 --- a/.netlify/www/index.html +++ b/.netlify/www/index.html @@ -1,23 +1,21 @@ + + OHIF Viewer: Deploy Preview + - - OHIF Viewer: Deploy Preview - - - -

Index of Previews

- - - + +

Index of Previews

+ + diff --git a/.node-version b/.node-version index 832d3850644..3876fd49864 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.14.0 +18.16.1 diff --git a/.prettierrc b/.prettierrc index 6a64b74e2d9..c7eeb08defb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,12 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, "singleQuote": true, - "arrowParens": "avoid" + "arrowParens": "avoid", + "singleAttributePerLine": true, + "endOfLine": "auto" } diff --git a/.webpack/helpers/excludeNodeModulesExcept.js b/.webpack/helpers/excludeNodeModulesExcept.js index f7234de1a24..ce4d3c3e29e 100644 --- a/.webpack/helpers/excludeNodeModulesExcept.js +++ b/.webpack/helpers/excludeNodeModulesExcept.js @@ -5,11 +5,11 @@ function excludeNodeModulesExcept(modules) { if (pathSep == '\\') // must be quoted for use in a regexp: pathSep = '\\\\'; - var moduleRegExps = modules.map(function(modName) { + var moduleRegExps = modules.map(function (modName) { return new RegExp('node_modules' + pathSep + modName); }); - return function(modulePath) { + return function (modulePath) { if (/node_modules/.test(modulePath)) { for (var i = 0; i < moduleRegExps.length; i++) if (moduleRegExps[i].test(modulePath)) return false; diff --git a/.webpack/rules/cssToJavaScript.js b/.webpack/rules/cssToJavaScript.js index 05365d31bcb..0cd60e909ba 100644 --- a/.webpack/rules/cssToJavaScript.js +++ b/.webpack/rules/cssToJavaScript.js @@ -1,9 +1,7 @@ const autoprefixer = require('autoprefixer'); const path = require('path'); const tailwindcss = require('tailwindcss'); -const tailwindConfigPath = path.resolve( - '../../platform/app/tailwind.config.js' -); +const tailwindConfigPath = path.resolve('../../platform/app/tailwind.config.js'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const devMode = process.env.NODE_ENV !== 'production'; diff --git a/.webpack/rules/stylusToJavaScript.js b/.webpack/rules/stylusToJavaScript.js new file mode 100644 index 00000000000..1d83f95281e --- /dev/null +++ b/.webpack/rules/stylusToJavaScript.js @@ -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; diff --git a/.webpack/rules/transpileJavaScript.js b/.webpack/rules/transpileJavaScript.js index dc84a4973e7..e6eb8d596fc 100644 --- a/.webpack/rules/transpileJavaScript.js +++ b/.webpack/rules/transpileJavaScript.js @@ -33,7 +33,11 @@ function transpileJavaScript(mode) { rootMode: 'upward', envName: mode, cacheCompression: false, - cacheDirectory: true, + // Note: This was causing a lot of issues with yarn link of the cornerstone + // only set this to true if you don't have a yarn link to external libs + // otherwise expect the lib changes not to be reflected in the dev server + // as it will be cached + cacheDirectory: false, }, }; } diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index ec8ec5291eb..6efedd0d2cd 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -7,8 +7,7 @@ const fs = require('fs'); const webpack = require('webpack'); // ~~ PLUGINS -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const TerserJSPlugin = require('terser-webpack-plugin'); // ~~ PackageJSON @@ -19,6 +18,7 @@ const loadShadersRule = require('./rules/loadShaders.js'); const loadWebWorkersRule = require('./rules/loadWebWorkers.js'); const transpileJavaScriptRule = require('./rules/transpileJavaScript.js'); const cssToJavaScript = require('./rules/cssToJavaScript.js'); +const stylusToJavaScript = require('./rules/stylusToJavaScript.js'); // ~~ ENV VARS const NODE_ENV = process.env.NODE_ENV; @@ -26,15 +26,34 @@ 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 VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || ''; -const COMMIT_HASH = - fs.readFileSync(path.join(__dirname, '../commit.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'); @@ -73,21 +92,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { children: false, warnings: true, }, - devServer: { - open: true, - port: 3000, - historyApiFallback: true, - headers: { - 'Cross-Origin-Embedder-Policy': 'require-corp', - 'Cross-Origin-Opener-Policy': 'same-origin', - }, - }, cache: { type: 'filesystem', }, module: { noParse: [/(codec)/, /(dicomicc)/], rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: 'source-map-loader', + }, transpileJavaScriptRule(mode), loadWebWorkersRule, // loadShadersRule, @@ -98,6 +113,9 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { }, }, cssToJavaScript, + // Note: Only uncomment the following if you are using the old style of stylus in v2 + // Also you need to uncomment this platform/app/.webpack/rules/extractStyleChunks.js + // stylusToJavaScript, { test: /\.wasm/, type: 'asset/resource', @@ -109,10 +127,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { alias: { // Viewer project '@': path.resolve(__dirname, '../platform/app/src'), - '@components': path.resolve( - __dirname, - '../platform/app/src/components' - ), + '@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'), @@ -135,29 +150,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { 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 }, + fallback: { + fs: false, + path: false, + zlib: false, + buffer: require.resolve('buffer'), + }, }, plugins: [ - new webpack.DefinePlugin({ - /* Application */ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), - 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), - 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), - 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), - 'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER), - 'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH), - /* i18n */ - 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), - 'process.env.LOCIZE_PROJECTID': JSON.stringify( - process.env.LOCIZE_PROJECTID || '' - ), - 'process.env.LOCIZE_API_KEY': JSON.stringify( - process.env.LOCIZE_API_KEY || '' - ), - 'process.env.REACT_APP_I18N_DEBUG': JSON.stringify( - process.env.REACT_APP_I18N_DEBUG || '' - ), + new webpack.DefinePlugin(defineValues), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], }), // Uncomment to generate bundle analyzer // new BundleAnalyzerPlugin(), diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..a37beb948be --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,543 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + + +### Bug Fixes + +* **i18n:** display set(s) are two words for English messages ([#3711](https://github.com/OHIF/Viewers/issues/3711)) ([c3a5847](https://github.com/OHIF/Viewers/commit/c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d)) + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + + +### Bug Fixes + +* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339)) + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + + +### Bug Fixes + +* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8)) + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + + +### Features + +* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6)) + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + + +### Features + +* **locale:** Added Turkish language support (tr-TR) - Community PR ([#3695](https://github.com/OHIF/Viewers/issues/3695)) ([745050a](https://github.com/OHIF/Viewers/commit/745050a28ec7c2ef2e9a4d4e590040050b2177b2)) + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + + +### Bug Fixes + +* **translation:** Side panel translate fix ([#3156](https://github.com/OHIF/Viewers/issues/3156)) ([29748d4](https://github.com/OHIF/Viewers/commit/29748d46a14d23817dbe196e0f64363fc61a8aed)) + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + + +### Bug Fixes + +* **cli:** Add npm packaged mode not working ([#3689](https://github.com/OHIF/Viewers/issues/3689)) ([28cec04](https://github.com/OHIF/Viewers/commit/28cec04ff43b81e218c3e9addef4665b3833a6fe)) + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + + +### Features + +* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89)) + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + + +### Bug Fixes + +* **editing:** regression bug in disable editing ([#3687](https://github.com/OHIF/Viewers/issues/3687)) ([4dc2acd](https://github.com/OHIF/Viewers/commit/4dc2acdefa872dd1d8df47f465e9e9656f95f67f)) + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + + +### Bug Fixes + +* **typescript error:** Change pubSubServiceInterface file type to typescript ([#3546](https://github.com/OHIF/Viewers/issues/3546)) ([eb22328](https://github.com/OHIF/Viewers/commit/eb22328fc05d06fc4411805e7a30f826659d796a)) + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + + +### Bug Fixes + +* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5)) + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + + +### Bug Fixes + +* **cli:** various fixes for adding custom modes and extensions ([#3683](https://github.com/OHIF/Viewers/issues/3683)) ([dc73b18](https://github.com/OHIF/Viewers/commit/dc73b187484da029a2664bb1302f30137c973b8c)) + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + + +### Bug Fixes + +* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5)) + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Bug Fixes + +* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a)) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + + +### Bug Fixes + +* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1)) + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + + +### Bug Fixes + +* **DicomJson:** retrieve.series.metadata method should be async ([#3659](https://github.com/OHIF/Viewers/issues/3659)) ([2737903](https://github.com/OHIF/Viewers/commit/2737903386cf97399473e0fa64fe53ad14da155a)) + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + + +### Bug Fixes + +* **suv:** import calculate-suv library version that prevents SUV calculation for a zero PatientWeight ([#3638](https://github.com/OHIF/Viewers/issues/3638)) ([0d10f46](https://github.com/OHIF/Viewers/commit/0d10f46b885fe54ec3dae1848134da658eb6280a)) + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + + +### Bug Fixes + +* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11)) + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + + +### Bug Fixes + +* **PT Metadata:** Allow for PatientWeight to be missing from the metadata ([#3621](https://github.com/OHIF/Viewers/issues/3621)) ([44f101d](https://github.com/OHIF/Viewers/commit/44f101d3f2b3204b67e31f4e4939062e65a246ee)) + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/Dockerfile b/Dockerfile index 500ad5ae10b..e6310c3ba34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # Stage 1: Build the application # docker build -t ohif/viewer:latest . -FROM node:16.15.0-slim as json-copier +FROM node:18.16.1-slim as json-copier RUN mkdir /usr/src/app WORKDIR /usr/src/app @@ -37,7 +37,8 @@ COPY platform /usr/src/app/platform #RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf # Copy Files -FROM node:16.15.0-slim as builder +FROM node:18.16.1-slim as builder +RUN apt-get update && apt-get install -y build-essential python3 RUN mkdir /usr/src/app WORKDIR /usr/src/app @@ -61,7 +62,7 @@ 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.23.1-alpine as final +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 @@ -69,5 +70,10 @@ 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;"] diff --git a/README.md b/README.md index 2e3d68b6870..5befea75c47 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

OHIF Medical Imaging Viewer

The OHIF Viewer is a zero-footprint medical image viewer -provided by the Open Health Imaging Foundation (OHIF). It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support DICOMweb.

+provided by the Open Health Imaging Foundation (OHIF). It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support DICOMweb.

@@ -38,7 +38,16 @@ provided by the Open Health Imaging Foundation (OHIF -![Alt text](platform/docs/docs/assets/img/OHIF-Viewer.jpg) + +| | | | +| :-: | :--- | :--- | +| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | +| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | +| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | +| Microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) | +| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | + + ## About @@ -85,17 +94,14 @@ forking). ### Support -We offer support through -[GitHub Issues](https://github.com/OHIF/Viewers/issues/new/choose). You can: - - [Report a Bug 🐛](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Report+%3Abug%3A%2CAwaiting+Reproduction&projects=&template=bug-report.yml&title=%5BBug%5D+) - [Request a Feature 🚀](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Request+%3Ahand%3A&projects=&template=feature-request.yml&title=%5BFeature+Request%5D+) - [Ask a Question 🤗](community.ohif.org) - [Slack Channel](https://join.slack.com/t/cornerstonejs/shared_invite/zt-1r8xb2zau-dOxlD6jit3TN0Uwf928w9Q) For commercial support, academic collaborations, and answers to common -questions; please read our -[documented FAQ](https://docs.ohif.org/faq/index.html#does-ohif-offer-commercial-support). +questions; please use [Get Support](https://ohif.org/get-support/) to contact +us. ## Developing @@ -128,9 +134,6 @@ Here is a schematic representation of our development workflow: - - - ### Requirements - [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install) @@ -164,7 +167,7 @@ yarn install These commands are available from the root directory. Each project directory also supports a number of commands that can be found in their respective -`README.md` and `project.json` files. +`README.md` and `package.json` files. | Yarn Commands | Description | | ---------------------------- | ------------------------------------------------------------- | @@ -222,15 +225,6 @@ you'll see the following: └── README.md # This file ``` -Want to better understand why and how we've structured this repository? Read -more about it in our [Architecture Documentation][ohif-architecture]. - - - -| Name | Description | Links | -| ---------------------------------------------------- | ----------------------------------------------------- | ---------------------- | -| [@ohif/extension-cornerstone][extension-cornerstone] | 2D image viewing, annotation, and segementation tools | [NPM][cornerstone-npm] | - ## Acknowledgments To acknowledge the OHIF Viewer in an academic publication, please cite @@ -259,7 +253,7 @@ or, for v1, please cite: > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) **Note:** If you use or find this repository helpful, please take the time to -star this repository on Github. This is an easy way for us to assess adoption +star this repository on GitHub. This is an easy way for us to assess adoption and it can help us obtain future funding for the project. This work is supported primarily by the National Institutes of Health, National @@ -267,7 +261,11 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program, under a [grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104). -This project is tested with BrowserStack. Thank you for supporting open source +[NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/) supported the development of new features and bug fixes marked with ["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority), +["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate) or ["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration). NCI Imaging Data Commons is supported by contract number 19X037Q from +Leidos Biomedical Research under Task Order HHSN26100071 from NCI. [IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized version of the OHIF Viewer. + +This project is tested with BrowserStack. Thank you for supporting open-source! ## License diff --git a/babel.config.js b/babel.config.js index d2aad74c276..b55cbfdfa0e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,22 +3,22 @@ const { extendDefaultPlugins } = require('svgo'); module.exports = { babelrcRoots: ['./platform/*', './extensions/*', './modes/*'], - presets: [ - '@babel/preset-env', - '@babel/preset-react', - '@babel/preset-typescript', - ], + presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], plugins: [ [ 'inline-react-svg', { svgo: { - plugins: extendDefaultPlugins([ + plugins: [ { - name: 'removeViewBox', - active: false, + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, }, - ]), + ], }, }, ], diff --git a/commit.txt b/commit.txt index 39fe445d11f..13d96924b9b 100644 --- a/commit.txt +++ b/commit.txt @@ -1 +1 @@ -1d38fe30a490010c7de487c7a0b1a5bfe3bc75a4 \ No newline at end of file +c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d \ No newline at end of file diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js deleted file mode 100644 index c4fdb9eb1cd..00000000000 --- a/extensions/_example/src/index.js +++ /dev/null @@ -1,138 +0,0 @@ -import ImageSet from '@ohif/core/src/classes/ImageSet'; -import { IWebApiDataSource } from '@ohif/core'; - -/** - * - */ -export default { - id: '@ohif/extension-*', - - /** - * LIFECYCLE HOOKS - */ - preRegistration() {}, - beforeExtInit() {}, - beforeExtDestroy() {}, - - /** - * MODULES - */ - getCommandsModule, - getContextModule, - getDataSourcesModule, - getLayoutTemplateModule, - getPanelModule, - getSopClassHandlerModule, - getToolbarModule() {}, - getViewportModule, -}; - -// appConfig, -// extensionConfig, -// dataSources, -// servicesManager, -// extensionManager, -// commandsManager, - -/** - * - */ -const getCommandsModule = () => ({ - definitions: { - exampleActionDef: { - commandFn: ({ param1 }) => { - console.log(`param1's value is: ${param1}`); - }, - // storeContexts: ['viewports'], - options: { param1: 'hello world' }, - context: 'VIEWER', // optional - }, - }, - defaultContext: 'ACTIVE_VIEWPORT::DICOMSR', -}); - -const ExampleContext = React.createContext(); - -function ExampleContextProvider({ children }) { - return ( - - {children} - - ); -} - -const getContextModule = () => [ - { - name: 'ExampleContext', - context: ExampleContext, - provider: ExampleContextProvider, - }, -]; - -const getDataSourcesModule = () => [ - { - name: 'exampleDataSource', - type: 'webApi', // 'webApi' | 'local' | 'other' - createDataSource: dataSourceConfig => { - return IWebApiDataSource.create(/* */); - }, - }, -]; - -const getLayoutTemplateModule = (/* ... */) => [ - { - id: 'exampleLayout', - name: 'exampleLayout', - component: ExampleLayoutComponent, - }, -]; - -const getPanelModule = () => { - return [ - { - name: 'exampleSidePanel', - iconName: 'info-circle-o', - iconLabel: 'Example', - label: 'Hello World', - isDisabled: studies => {}, // optional - component: ExamplePanelContentComponent, - }, - ]; -}; - -const getSopClassHandlerModule = (/* ... */) => { - const BASIC_TEXT_SR = '1.2.840.10008.5.1.4.1.1.88.11'; - - return [ - { - name: 'ExampleSopClassHandle', - sopClassUids: [BASIC_TEXT_SR], - getDisplaySetsFromSeries: instances => { - const imageSet = new ImageSet(instances); - - imageSet.setAttributes(/** */); - imageSet.sortBy((a, b) => 0); - - return imageSet; - }, - }, - ]; -}; - -const getToolbarModule = () => {}; - -// displaySet, viewportIndex, dataSource -const getViewportModule = () => { - const wrappedViewport = props => { - return ( - { - commandsManager.runCommand('commandName', data); - }} - /> - ); - }; - - return [{ name: 'example', component: wrappedViewport }]; -}; diff --git a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js index 5182c4a6a98..4ee5cc9fc1c 100644 --- a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js @@ -3,8 +3,7 @@ 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 BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-rt/CHANGELOG.md b/extensions/cornerstone-dicom-rt/CHANGELOG.md new file mode 100644 index 00000000000..55c8050eff9 --- /dev/null +++ b/extensions/cornerstone-dicom-rt/CHANGELOG.md @@ -0,0 +1,425 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt diff --git a/extensions/cornerstone-dicom-rt/babel.config.js b/extensions/cornerstone-dicom-rt/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/extensions/cornerstone-dicom-rt/babel.config.js +++ b/extensions/cornerstone-dicom-rt/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-rt/package.json b/extensions/cornerstone-dicom-rt/package.json index 8eb9639214f..d80989d0e4b 100644 --- a/extensions/cornerstone-dicom-rt/package.json +++ b/extensions/cornerstone-dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-rt", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "DICOM RT read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/i18n": "3.6.0", + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/i18n": "3.7.0-beta.108", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js index 81ba6aab99b..a7a232f1985 100644 --- a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js @@ -7,11 +7,7 @@ const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -56,10 +52,7 @@ function _getDisplaySetsFromSeries( }; let referencedSeriesSequence = instance.ReferencedSeriesSequence; - if ( - instance.ReferencedFrameOfReferenceSequence && - !instance.ReferencedSeriesSequence - ) { + if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) { instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence( instance.ReferencedFrameOfReferenceSequence ); @@ -72,8 +65,7 @@ function _getDisplaySetsFromSeries( const referencedSeries = referencedSeriesSequence[0]; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -88,14 +80,12 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; return referencedDisplaySet; }; - displaySet.load = ({ headers }) => - _load(displaySet, servicesManager, extensionManager, headers); + displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers); return [displaySet]; } @@ -194,11 +184,7 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { name: 'dicom-rt', sopClassUids, getDisplaySetsFromSeries: instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }, }, ]; diff --git a/extensions/cornerstone-dicom-rt/src/index.tsx b/extensions/cornerstone-dicom-rt/src/index.tsx index 7d552977674..953c9a7c7da 100644 --- a/extensions/cornerstone-dicom-rt/src/index.tsx +++ b/extensions/cornerstone-dicom-rt/src/index.tsx @@ -4,9 +4,7 @@ import { Types } from '@ohif/core'; import getSopClassHandlerModule from './getSopClassHandlerModule'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport'); }); const OHIFCornerstoneRTViewport = props => { @@ -36,12 +34,14 @@ const extension: Types.Extensions.Extension = { getViewportModule({ servicesManager, extensionManager, + commandsManager, }: Types.Extensions.ExtensionParams) { const ExtendedOHIFCornerstoneRTViewport = props => { return ( ); diff --git a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js index 613c2fb65c2..cf58eb40a2e 100644 --- a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js +++ b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js @@ -25,23 +25,13 @@ async function checkAndLoadContourData(instance, datasource) { if (Array.isArray(contourData)) { promisesMap.has(referencedROINumber) - ? promisesMap - .get(referencedROINumber) - .push(Promise.resolve(contourData)) - : promisesMap.set(referencedROINumber, [ - Promise.resolve(contourData), - ]); + ? 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' - ); + if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) { + return Promise.reject('Invalid datasource object or retrieve function'); } const bulkDataPromise = datasource.retrieve.bulkDataURI({ @@ -74,10 +64,7 @@ async function checkAndLoadContourData(instance, datasource) { ROIContour.ContourSequence.forEach((Contour, index) => { const promise = resolvedPromises[index]; if (promise.status === 'fulfilled') { - if ( - Array.isArray(promise.value) && - promise.value.every(Number.isFinite) - ) { + if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) { // If promise.value is already an array of numbers, use it directly Contour.ContourData = promise.value; } else { @@ -85,13 +72,8 @@ async function checkAndLoadContourData(instance, datasource) { 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); + if (typeof dataUint8Array === 'string' && dataUint8Array.includes('\\')) { + Contour.ContourData = dataUint8Array.split('\\').map(parseFloat); } else { Contour.ContourData = []; } @@ -120,9 +102,8 @@ export default async function loadRTStruct( const { bulkDataURI } = dataSource.getConfig?.() || {}; const { dicomLoaderService } = utilityModule.exports; - const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet( - referencedDisplaySet - ); + const imageIdSopInstanceUidPairs = + _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet); // Set here is loading is asynchronous. // If this function throws its set back to false. @@ -137,20 +118,14 @@ export default async function loadRTStruct( ); const dicomData = DicomMessage.readFile(segArrayBuffer); - const rtStructDataset = DicomMetaDictionary.naturalizeDataset( - dicomData.dict - ); + const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); instance = rtStructDataset; } else { await checkAndLoadContourData(instance, dataSource); } - const { - StructureSetROISequence, - ROIContourSequence, - RTROIObservationsSequence, - } = instance; + const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance; // Define our structure set entry and add it to the rtstruct module state. const structureSet = { @@ -174,19 +149,9 @@ export default async function loadRTStruct( const contourPoints = []; for (let c = 0; c < ContourSequenceArray.length; c++) { - const { - ContourImageSequence, - ContourData, - NumberOfContourPoints, - ContourGeometricType, - } = ContourSequenceArray[c]; - - const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID; - const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); - - if (!imageId) { - continue; - } + const { ContourImageSequence, ContourData, NumberOfContourPoints, ContourGeometricType } = + ContourSequenceArray[c]; + let isSupported = false; const points = []; @@ -235,9 +200,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => { imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID ); - return imageIdSopInstanceUidPairsEntry - ? imageIdSopInstanceUidPairsEntry.imageId - : null; + return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null; }; function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) { @@ -258,8 +221,7 @@ function _setROIContourMetadata( isSupported ) { const StructureSetROI = StructureSetROISequence.find( - structureSetROI => - structureSetROI.ROINumber === ROIContour.ReferencedROINumber + structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber ); const ROIContourData = { @@ -299,23 +261,15 @@ function _setROIContourDataColor(ROIContour, ROIContourData) { } } -function _setROIContourRTROIObservations( - ROIContourData, - RTROIObservationsSequence, - ROINumber -) { +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; + const { ObservationNumber, ROIObservationDescription, RTROIInterpretedType, ROIInterpreter } = + RTROIObservations; ROIContourData.RTROIObservations = { ObservationNumber, diff --git a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts deleted file mode 100644 index 0668c894b82..00000000000 --- a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts +++ /dev/null @@ -1,70 +0,0 @@ -async function _hydrateRTDisplaySet({ - rtDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForRTDisplaySet( - rtDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(rtDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - rtDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - return true; -} - -export default _hydrateRTDisplaySet; diff --git a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts index 826e3b9a6f3..f47f0089d8a 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts @@ -1,12 +1,7 @@ -function createRTToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createRTToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 4af78caeeb7..91492cbfbed 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -1,4 +1,4 @@ -import hydrateRTDisplaySet from './_hydrateRT'; +import { ButtonEnums } from '@ohif/ui'; const RESPONSE = { NO_NEVER: -1, @@ -9,21 +9,24 @@ const RESPONSE = { function promptHydrateRT({ servicesManager, rtDisplaySet, - viewportIndex, + viewportId, toolGroupId = 'default', + preHydrateCallbacks, + hydrateRTDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { + preHydrateCallbacks?.forEach(callback => { + callback(); + }); + const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, toolGroupId, servicesManager, }); @@ -33,17 +36,17 @@ function promptHydrateRT({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { - type: 'secondary', + type: ButtonEnums.type.secondary, text: 'No', value: RESPONSE.CANCEL, }, { - type: 'primary', + type: ButtonEnums.type.primary, text: 'Yes', value: RESPONSE.HYDRATE_SEG, }, @@ -54,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index 58d190f5a86..e9c7450c69a 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -1,17 +1,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import OHIF, { utils } from '@ohif/core'; -import { - ViewportActionBar, - useViewportGrid, - LoadingIndicatorTotalPercent, -} from '@ohif/ui'; +import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui'; -import _hydrateRTdisplaySet from '../utils/_hydrateRT'; import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; import createRTToolGroupAndAddTools from '../utils/initRTToolGroup'; -import _hydrateRTDisplaySet from '../utils/_hydrateRT'; const { formatDate } = utils; const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup'; @@ -21,10 +15,10 @@ function OHIFCornerstoneRTViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, + commandsManager, } = props; const { @@ -35,7 +29,9 @@ function OHIFCornerstoneRTViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const viewportId = viewportOptions.viewportId; + + const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`; // RT viewport will always have a single display set if (displaySets.length > 1) { @@ -66,12 +62,10 @@ function OHIFCornerstoneRTViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet(); - const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( - referencedDisplaySet - ); + const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet); referencedDisplaySetRef.current = { displaySet: referencedDisplaySet, @@ -91,14 +85,27 @@ function OHIFCornerstoneRTViewport(props) { setElement(null); }; + const storePresentationState = useCallback(() => { + viewportGrid?.viewports.forEach(({ viewportId }) => { + commandsManager.runCommand('storePresentation', { + viewportId, + }); + }); + }, [viewportGrid]); + + const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [rtDisplaySet], + viewportId, + }); + }; + const getCornerstoneViewport = useCallback(() => { const { component: Component } = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -115,7 +122,7 @@ function OHIFCornerstoneRTViewport(props) { onElementDisabled={onElementDisabled} > ); - }, [viewportIndex, rtDisplaySet, toolGroupId]); + }, [viewportId, rtDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -136,11 +143,7 @@ function OHIFCornerstoneRTViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -153,31 +156,29 @@ function OHIFCornerstoneRTViewport(props) { promptHydrateRT({ servicesManager, - viewportIndex, + viewportId, rtDisplaySet, + preHydrateCallbacks: [storePresentationState], + hydrateRTDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]); + }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.rtDisplaySet.displaySetInstanceUID === - rtDisplaySet.displaySetInstanceUID - ) { + 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', + message: 'Overlapping segments detected which is not currently supported', type: 'warning', }); } @@ -212,12 +213,10 @@ function OHIFCornerstoneRTViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -236,19 +235,13 @@ function OHIFCornerstoneRTViewport(props) { return; } - toolGroup = createRTToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); + toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); setToolGroupCreated(true); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); toolGroupService.destroyToolGroup(toolGroupId); }; @@ -259,9 +252,7 @@ function OHIFCornerstoneRTViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [rtDisplaySet]); @@ -282,7 +273,7 @@ function OHIFCornerstoneRTViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -303,10 +294,16 @@ function OHIFCornerstoneRTViewport(props) { } = referencedDisplaySetRef.current.metadata; const onStatusClick = async () => { - const isHydrated = await _hydrateRTDisplaySet({ + // Before hydrating a RT and make it added to all viewports in the grid + // that share the same frameOfReferenceUID, we need to store the viewport grid + // presentation state, so that we can restore it after hydrating the RT. This is + // required if the user has changed the viewport (other viewport than RT viewport) + // presentation state (w/l and invert) and then opens the RT. If we don't store + // the presentation state, the viewport will be reset to the default presentation + storePresentationState(); + const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); @@ -333,26 +330,22 @@ function OHIFCornerstoneRTViewport(props) { currentSeries: SeriesNumber, seriesDescription: `RT Viewport ${SeriesDescription}`, patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{rtIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; case false: - StatusIcon = () => ; + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load RTSTRUCT.
; } const StatusArea = () => ( -
-
+
+
RTSTRUCT
{!isHydrated && (
@@ -44,7 +47,10 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js index 017e4bbf014..3f6eb4b69ea 100644 --- a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js @@ -40,21 +40,15 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + 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`, - // }), + new MiniCssExtractPlugin({ + filename: `./dist/${outputName}.css`, + chunkFilename: `./dist/${outputName}.css`, + }), ], }); }; diff --git a/extensions/cornerstone-dicom-seg/CHANGELOG.md b/extensions/cornerstone-dicom-seg/CHANGELOG.md new file mode 100644 index 00000000000..e7400a0b8bd --- /dev/null +++ b/extensions/cornerstone-dicom-seg/CHANGELOG.md @@ -0,0 +1,444 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + + +### Bug Fixes + +* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8)) + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg diff --git a/extensions/cornerstone-dicom-seg/babel.config.js b/extensions/cornerstone-dicom-seg/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/extensions/cornerstone-dicom-seg/babel.config.js +++ b/extensions/cornerstone-dicom-seg/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index cc7eafdcac7..064b4b059ce 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-seg", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "DICOM SEG read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/i18n": "3.6.0", + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/i18n": "3.7.0-beta.108", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -44,6 +44,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", + "@cornerstonejs/adapters": "^1.20.3", + "@cornerstonejs/tools": "^1.20.3", + "@kitware/vtk.js": "27.3.1", "react-color": "^2.19.3" } } diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts new file mode 100644 index 00000000000..1926ee9f138 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -0,0 +1,435 @@ +import dcmjs from 'dcmjs'; +import { createReportDialogPrompt } from '@ohif/extension-default'; +import { ServicesManager, Types } from '@ohif/core'; +import { cache, metaData } from '@cornerstonejs/core'; +import { + segmentation as cornerstoneToolsSegmentation, + Enums as cornerstoneToolsEnums, +} from '@cornerstonejs/tools'; +import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters'; +import { classes, DicomMetadataStore } from '@ohif/core'; + +import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchingSquares'; +import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; +import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; + +import { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +} from './utils/hydrationUtils'; + +const { datasetToBlob } = dcmjs.data; + +const { + Cornerstone3D: { + Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation }, + }, +} = adaptersSEG; + +const { + Cornerstone3D: { + RTSS: { generateRTSSFromSegmentations }, + }, +} = adaptersRT; + +const { downloadDICOMData } = helpers; + +const commandsModule = ({ + servicesManager, + extensionManager, +}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => { + const { + uiNotificationService, + segmentationService, + uiDialogService, + displaySetService, + viewportGridService, + } = (servicesManager as ServicesManager).services; + + const actions = { + /** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ + getUpdatedViewportsForSegmentation, + /** + * Creates an empty segmentation for a specified viewport. + * It first checks if the display set associated with the viewport is reconstructable. + * If not, it raises a notification error. Otherwise, it creates a new segmentation + * for the display set after handling the necessary steps for making the viewport + * a volume viewport first + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the target viewport ID. + * + */ + createEmptySegmentationForViewport: async ({ viewportId }) => { + const viewport = getTargetViewport({ viewportId, viewportGridService }); + // Todo: add support for multiple display sets + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + + if (!displaySet.isReconstructable) { + uiNotificationService.show({ + title: 'Segmentation', + message: 'Segmentation is not supported for non-reconstructible displaysets yet', + type: 'error', + }); + return; + } + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + const currentSegmentations = segmentationService.getSegmentations(); + const segmentationId = await segmentationService.createSegmentationForDisplaySet( + displaySetInstanceUID, + { label: `Segmentation ${currentSegmentations.length + 1}` } + ); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + // Add only one segment for now + segmentationService.addSegment(segmentationId, { + toolGroupId, + segmentIndex: 1, + properties: { + label: 'Segment 1', + }, + }); + + return segmentationId; + }, + }); + }, + /** + * Loads segmentations for a specified viewport. + * The function prepares the viewport for rendering, then loads the segmentation details. + * Additionally, if the segmentation has scalar data, it is set for the corresponding label map volume. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentations - Array of segmentations to be loaded. + * @param params.viewportId - the target viewport ID. + * + */ + loadSegmentationsForViewport: async ({ segmentations, viewportId }) => { + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + // Todo: handle adding more than one segmentation + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const segmentation = segmentations[0]; + const segmentationId = segmentation.id; + const label = segmentation.label; + const segments = segmentation.segments; + + delete segmentation.segments; + + await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, { + segmentationId, + label, + }); + + if (segmentation.scalarData) { + const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId); + labelmapVolume.scalarData.set(segmentation.scalarData); + } + + segmentationService.addOrUpdateSegmentation(segmentation); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + segments.forEach(segment => { + if (segment === null) { + return; + } + segmentationService.addSegment(segmentationId, { + segmentIndex: segment.segmentIndex, + toolGroupId, + properties: { + color: segment.color, + label: segment.label, + opacity: segment.opacity, + isLocked: segment.isLocked, + visibility: segment.isVisible, + active: segmentation.activeSegmentIndex === segment.segmentIndex, + }, + }); + }); + + if (segmentation.centroidsIJK) { + segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK); + } + + return segmentationId; + }, + }); + }, + /** + * Loads segmentation display sets for a specified viewport. + * Depending on the modality of the display set (SEG or RTSTRUCT), + * it chooses the appropriate service function to create + * the segmentation for the display set. + * The function then prepares the viewport for rendering segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded. + * @param params.displaySets - Array of display sets to be loaded for segmentation. + * + */ + loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => { + // Todo: handle adding more than one segmentation + const displaySet = displaySets[0]; + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID: displaySet.referencedDisplaySetInstanceUID, + loadFn: async () => { + const segDisplaySet = displaySet; + const suppressEvents = false; + const serviceFunction = + segDisplaySet.Modality === 'SEG' + ? 'createSegmentationForSEGDisplaySet' + : 'createSegmentationForRTDisplaySet'; + + const boundFn = segmentationService[serviceFunction].bind(segmentationService); + const segmentationId = await boundFn(segDisplaySet, null, suppressEvents); + + return segmentationId; + }, + }); + }, + /** + * Generates a segmentation from a given segmentation ID. + * This function retrieves the associated segmentation and + * its referenced volume, extracts label maps from the + * segmentation volume, and produces segmentation data + * alongside associated metadata. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be generated. + * @param params.options - Optional configuration for the generation process. + * + * @returns Returns the generated segmentation data. + */ + generateSegmentation: ({ segmentationId, options = {} }) => { + const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId); + + const { referencedVolumeId } = segmentation.representationData.LABELMAP; + + const segmentationVolume = cache.getVolume(segmentationId); + const referencedVolume = cache.getVolume(referencedVolumeId); + const referencedImages = referencedVolume.getCornerstoneImages(); + + const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume); + + // Generate fake metadata as an example + labelmapObj.metadata = []; + + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => { + // segmentation service already has a color for each segment + const segment = segmentationInOHIF?.segments[segmentIndex]; + const { label, color } = segment; + + const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB( + color.slice(0, 3).map(value => value / 255) + ).map(value => Math.round(value)); + + const segmentMetadata = { + SegmentNumber: segmentIndex.toString(), + SegmentLabel: label, + SegmentAlgorithmType: 'MANUAL', + SegmentAlgorithmName: 'OHIF Brush', + RecommendedDisplayCIELabValue, + SegmentedPropertyCategoryCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + SegmentedPropertyTypeCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + }; + labelmapObj.metadata[segmentIndex] = segmentMetadata; + }); + + const generatedSegmentation = generateSegmentation( + referencedImages, + labelmapObj, + metaData, + options + ); + + return generatedSegmentation; + }, + /** + * Downloads a segmentation based on the provided segmentation ID. + * This function retrieves the associated segmentation and + * uses it to generate the corresponding DICOM dataset, which + * is then downloaded with an appropriate filename. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be downloaded. + * + */ + downloadSegmentation: ({ segmentationId }) => { + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + const generatedSegmentation = actions.generateSegmentation({ + segmentationId, + }); + + downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`); + }, + /** + * Stores a segmentation based on the provided segmentationId into a specified data source. + * The SeriesDescription is derived from user input or defaults to the segmentation label, + * and in its absence, defaults to 'Research Derived Series'. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be stored. + * @param params.dataSource - Data source where the generated segmentation will be stored. + * + * @returns {Object|void} Returns the naturalized report if successfully stored, + * otherwise throws an error. + */ + storeSegmentation: async ({ segmentationId, dataSource }) => { + const promptResult = await createReportDialogPrompt(uiDialogService, { + extensionManager, + }); + + if (promptResult.action !== 1 && promptResult.value) { + return; + } + + const segmentation = segmentationService.getSegmentation(segmentationId); + + if (!segmentation) { + throw new Error('No segmentation found'); + } + + const { label } = segmentation; + const SeriesDescription = promptResult.value || label || 'Research Derived Series'; + + const generatedData = actions.generateSegmentation({ + segmentationId, + options: { + SeriesDescription, + }, + }); + + if (!generatedData || !generatedData.dataset) { + throw new Error('Error during segmentation generation'); + } + + const { dataset: naturalizedReport } = generatedData; + + await dataSource.store.dicom(naturalizedReport); + + // The "Mode" route listens for DicomMetadataStore changes + // When a new instance is added, it listens and + // automatically calls makeDisplaySets + + // add the information for where we stored it to the instance as well + naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot; + + DicomMetadataStore.addInstances([naturalizedReport], true); + + return naturalizedReport; + }, + /** + * Converts segmentations into RTSS for download. + * This sample function retrieves all segentations and passes to + * cornerstone tool adapter to convert to DICOM RTSS format. It then + * converts dataset to downloadable blob. + * + */ + downloadRTSS: ({ segmentationId }) => { + const segmentations = segmentationService.getSegmentation(segmentationId); + const vtkUtils = { + vtkImageMarchingSquares, + vtkDataArray, + vtkImageData, + }; + + const RTSS = generateRTSSFromSegmentations( + segmentations, + classes.MetadataProvider, + DicomMetadataStore, + cache, + cornerstoneToolsEnums, + vtkUtils + ); + + try { + const reportBlob = datasetToBlob(RTSS); + + //Create a URL for the binary. + const objectUrl = URL.createObjectURL(reportBlob); + window.location.assign(objectUrl); + } catch (e) { + console.warn(e); + } + }, + }; + + const definitions = { + getUpdatedViewportsForSegmentation: { + commandFn: actions.getUpdatedViewportsForSegmentation, + }, + loadSegmentationDisplaySetsForViewport: { + commandFn: actions.loadSegmentationDisplaySetsForViewport, + }, + loadSegmentationsForViewport: { + commandFn: actions.loadSegmentationsForViewport, + }, + createEmptySegmentationForViewport: { + commandFn: actions.createEmptySegmentationForViewport, + }, + generateSegmentation: { + commandFn: actions.generateSegmentation, + }, + downloadSegmentation: { + commandFn: actions.downloadSegmentation, + }, + storeSegmentation: { + commandFn: actions.storeSegmentation, + }, + downloadRTSS: { + commandFn: actions.downloadRTSS, + }, + }; + + return { + actions, + definitions, + }; +}; + +export default commandsModule; diff --git a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts index 0a2f888e4f8..f61136245d1 100644 --- a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const segProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'Segmentations', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx new file mode 100644 index 00000000000..e626c87d7bf --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import { useAppConfig } from '@state'; +import PanelSegmentation from './panels/PanelSegmentation'; +import SegmentationToolbox from './panels/SegmentationToolbox'; + +const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => { + const { customizationService } = servicesManager.services; + + const wrappedPanelSegmentation = configuration => { + const [appConfig] = useAppConfig(); + + const disableEditingForMode = customizationService.get('segmentation.disableEditing'); + + return ( + + ); + }; + + const wrappedPanelSegmentationWithTools = configuration => { + const [appConfig] = useAppConfig(); + return ( + <> + + + + ); + }; + + return [ + { + name: 'panelSegmentation', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentation, + }, + { + name: 'panelSegmentationWithTools', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentationWithTools, + }, + ]; +}; + +export default getPanelModule; diff --git a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js index b92da538290..7548d492ea8 100644 --- a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js @@ -1,21 +1,15 @@ -import vtkMath from '@kitware/vtk.js/Common/Core/Math'; - import { utils } from '@ohif/core'; +import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core'; +import { adaptersSEG, Enums } from '@cornerstonejs/adapters'; import { SOPClassHandlerId } from './id'; -import dcmjs from 'dcmjs'; - -const { DicomMessage, DicomMetaDictionary } = dcmjs.data; +import { dicomlabToRGB } from './utils/dicomlabToRGB'; const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -63,13 +57,13 @@ function _getDisplaySetsFromSeries( const referencedSeriesSequence = instance.ReferencedSeriesSequence; if (!referencedSeriesSequence) { - throw new Error('ReferencedSeriesSequence is missing for the SEG'); + console.error('ReferencedSeriesSequence is missing for the SEG'); + return; } - const referencedSeries = referencedSeriesSequence[0]; + const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -84,8 +78,7 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; // Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc. displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID; @@ -118,17 +111,13 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { // We don't want to fire multiple loads, so we'll wait for the first to finish // 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 - ) { - const segments = await _loadSegments( + if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) { + await _loadSegments({ extensionManager, + servicesManager, segDisplaySet, - headers - ); - - segDisplaySet.segments = segments; + headers, + }); } const suppressEvents = true; @@ -147,152 +136,61 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { return loadPromises[SOPInstanceUID]; } -async function _loadSegments(extensionManager, segDisplaySet, headers) { +async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers }) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.common' ); + const { segmentationService } = servicesManager.services; + const { dicomLoaderService } = utilityModule.exports; - const segArrayBuffer = await dicomLoaderService.findDicomDataPromise( - segDisplaySet, - null, - headers - ); + const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers); - const dicomData = DicomMessage.readFile(segArrayBuffer); - const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); - dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); + const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId); - if (!Array.isArray(dataset.SegmentSequence)) { - dataset.SegmentSequence = [dataset.SegmentSequence]; + if (!cachedReferencedVolume) { + throw new Error( + 'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet' + ); } - const segments = _getSegments(dataset); - return segments; -} + const { imageIds } = cachedReferencedVolume; -function _segmentationExists(segDisplaySet, segmentationService) { - // This should be abstracted with the CornerstoneCacheService - return segmentationService.getSegmentation( - segDisplaySet.displaySetInstanceUID - ); -} + // Todo: what should be defaults here + const tolerance = 0.001; + const skipOverlapping = true; -function _getPixelData(dataset, segments) { - let frameSize = Math.ceil((dataset.Rows * dataset.Columns) / 8); - let nextOffset = 0; - - Object.keys(segments).forEach(segmentKey => { - const segment = segments[segmentKey]; - segment.numberOfFrames = segment.functionalGroups.length; - segment.size = segment.numberOfFrames * frameSize; - segment.offset = nextOffset; - nextOffset = segment.offset + segment.size; - const packedSegment = dataset.PixelData[0].slice( - segment.offset, - nextOffset - ); - - segment.pixelData = dcmjs.data.BitArray.unpack(packedSegment); - segment.geometry = geometryFromFunctionalGroups( - dataset, - segment.functionalGroups - ); + eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => { + const { percentComplete } = evt.detail; + segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, { + percentComplete, + }); }); - return segments; -} - -function geometryFromFunctionalGroups(dataset, perFrame) { - let pixelMeasures = - dataset.SharedFunctionalGroupsSequence.PixelMeasuresSequence; - let planeOrientation = - dataset.SharedFunctionalGroupsSequence.PlaneOrientationSequence; - let planePosition = perFrame[0].PlanePositionSequence; // TODO: assume sorted frames! - - const geometry = {}; - - // NB: DICOM PixelSpacing is defined as Row then Column, - // unlike ImageOrientationPatient - let spacingBetweenSlices = pixelMeasures.SpacingBetweenSlices; - if (!spacingBetweenSlices) { - if (pixelMeasures.SliceThickness) { - console.log('Using SliceThickness as SpacingBetweenSlices'); - spacingBetweenSlices = pixelMeasures.SliceThickness; - } - } - geometry.spacing = [ - pixelMeasures.PixelSpacing[1], - pixelMeasures.PixelSpacing[0], - spacingBetweenSlices, - ].map(Number); - - geometry.dimensions = [dataset.Columns, dataset.Rows, perFrame.length].map( - Number - ); - - let orientation = planeOrientation.ImageOrientationPatient.map(Number); - const columnStepToPatient = orientation.slice(0, 3); - const rowStepToPatient = orientation.slice(3, 6); - geometry.planeNormal = []; - vtkMath.cross(columnStepToPatient, rowStepToPatient, geometry.planeNormal); - - let firstPosition = perFrame[0].PlanePositionSequence.ImagePositionPatient.map( - Number + const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState( + imageIds, + arrayBuffer, + metaData, + { skipOverlapping, tolerance, eventTarget, triggerEvent } ); - let lastPosition = perFrame[ - perFrame.length - 1 - ].PlanePositionSequence.ImagePositionPatient.map(Number); - geometry.sliceStep = []; - vtkMath.subtract(lastPosition, firstPosition, geometry.sliceStep); - vtkMath.normalize(geometry.sliceStep); - geometry.direction = columnStepToPatient - .concat(rowStepToPatient) - .concat(geometry.sliceStep); - geometry.origin = planePosition.ImagePositionPatient.map(Number); - - return geometry; -} - -function _getSegments(dataset) { - const segments = {}; - dataset.SegmentSequence.forEach(segment => { - const cielab = segment.RecommendedDisplayCIELabValue; - const rgba = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => - Math.round(x * 255) - ); - - rgba.push(255); - const segmentNumber = segment.SegmentNumber; - - segments[segmentNumber] = { - color: rgba, - functionalGroups: [], - offset: null, - size: null, - pixelData: null, - label: segment.SegmentLabel, - }; + results.segMetadata.data.forEach((data, i) => { + if (i > 0) { + data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue); + } }); - // make a list of functional groups per segment - dataset.PerFrameFunctionalGroupsSequence.forEach(functionalGroup => { - const segmentNumber = - functionalGroup.SegmentIdentificationSequence.ReferencedSegmentNumber; - segments[segmentNumber].functionalGroups.push(functionalGroup); - }); + Object.assign(segDisplaySet, results); +} - return _getPixelData(dataset, segments); +function _segmentationExists(segDisplaySet, segmentationService) { + // This should be abstracted with the CornerstoneCacheService + return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID); } function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ diff --git a/extensions/cornerstone-dicom-seg/src/index.tsx b/extensions/cornerstone-dicom-seg/src/index.tsx index 04ce5f98cb6..bb6a6d4b118 100644 --- a/extensions/cornerstone-dicom-seg/src/index.tsx +++ b/extensions/cornerstone-dicom-seg/src/index.tsx @@ -1,16 +1,14 @@ import { id } from './id'; import React from 'react'; -import { Types } from '@ohif/core'; - import getSopClassHandlerModule from './getSopClassHandlerModule'; -import PanelSegmentation from './panels/PanelSegmentation'; import getHangingProtocolModule from './getHangingProtocolModule'; +import getPanelModule from './getPanelModule'; +import getCommandsModule from './commandsModule'; +import preRegistration from './init'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport'); }); const OHIFCornerstoneSEGViewport = props => { @@ -30,6 +28,7 @@ const extension = { * You ID can be anything you want, but it should be unique. */ id, + preRegistration, /** * PanelModule should provide a list of panels that will be available in OHIF @@ -37,31 +36,8 @@ const extension = { * iconName, iconLabel, label, component} object. Example of a panel module * is the StudyBrowserPanel that is provided by the default extension in OHIF. */ - getPanelModule: ({ - servicesManager, - commandsManager, - extensionManager, - }): Types.Panel[] => { - const wrappedPanelSegmentation = () => { - return ( - - ); - }; - - return [ - { - name: 'panelSegmentation', - iconName: 'tab-segmentation', - iconLabel: 'Segmentation', - label: 'Segmentation', - component: wrappedPanelSegmentation, - }, - ]; - }, + getPanelModule, + getCommandsModule, getViewportModule({ servicesManager, extensionManager }) { const ExtendedOHIFCornerstoneSEGViewport = props => { @@ -69,14 +45,13 @@ const extension = { ); }; - return [ - { name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }, - ]; + return [{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }]; }, /** * SopClassHandlerModule should provide a list of sop class handlers that will be @@ -88,4 +63,4 @@ const extension = { getHangingProtocolModule, }; -export default extension; \ No newline at end of file +export default extension; diff --git a/extensions/cornerstone-dicom-seg/src/init.ts b/extensions/cornerstone-dicom-seg/src/init.ts new file mode 100644 index 00000000000..9702aa570b6 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/init.ts @@ -0,0 +1,5 @@ +import { addTool, BrushTool } from '@cornerstonejs/tools'; + +export default function init({ configuration = {} }): void { + addTool(BrushTool); +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx index bc645652a7f..381fa7cc569 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx @@ -1,48 +1,28 @@ +import { createReportAsync } from '@ohif/extension-default'; import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { SegmentationGroupTable } from '@ohif/ui'; -import callInputDialog from './callInputDialog'; +import { SegmentationGroupTable, LegacyButtonGroup, LegacyButton } from '@ohif/ui'; +import callInputDialog from './callInputDialog'; +import callColorPickerDialog from './colorPickerDialog'; import { useTranslation } from 'react-i18next'; export default function PanelSegmentation({ servicesManager, commandsManager, + extensionManager, + configuration, }) { - const { segmentationService, uiDialogService } = servicesManager.services; + const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services; const { t } = useTranslation('PanelSegmentation'); + const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); const [segmentationConfiguration, setSegmentationConfiguration] = useState( segmentationService.getConfiguration() ); - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); - - const [isMinimized, setIsMinimized] = useState({}); - - const onToggleMinimizeSegmentation = useCallback( - id => { - setIsMinimized(prevState => ({ - ...prevState, - [id]: !prevState[id], - })); - }, - [setIsMinimized] - ); - - // Only expand the last segmentation added to the list and collapse the rest - useEffect(() => { - const lastSegmentationId = segmentations[segmentations.length - 1]?.id; - if (lastSegmentationId) { - setIsMinimized(prevState => ({ - ...prevState, - [lastSegmentationId]: false, - })); - } - }, [segmentations, setIsMinimized]); + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); useEffect(() => { // ~~ Subscription @@ -67,6 +47,16 @@ export default function PanelSegmentation({ }; }, []); + const getToolGroupIds = segmentationId => { + const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId); + + return toolGroupIds; + }; + + const onSegmentationAdd = async () => { + commandsManager.runCommand('createEmptySegmentationForViewport'); + }; + const onSegmentationClick = (segmentationId: string) => { segmentationService.setActiveSegmentationForToolGroup(segmentationId); }; @@ -75,33 +65,19 @@ export default function PanelSegmentation({ segmentationService.remove(segmentationId); }; - const getToolGroupIds = segmentationId => { - const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation( - segmentationId - ); - - return toolGroupIds; + const onSegmentAdd = segmentationId => { + segmentationService.addSegment(segmentationId); }; const onSegmentClick = (segmentationId, segmentIndex) => { - segmentationService.setActiveSegmentForSegmentation( - segmentationId, - segmentIndex - ); + segmentationService.setActiveSegment(segmentationId, segmentIndex); const toolGroupIds = getToolGroupIds(segmentationId); toolGroupIds.forEach(toolGroupId => { // const toolGroupId = - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); - segmentationService.jumpToSegmentCenter( - segmentationId, - segmentIndex, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); + segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId); }); }; @@ -116,11 +92,7 @@ export default function PanelSegmentation({ return; } - segmentationService.setSegmentLabelForSegmentation( - segmentationId, - segmentIndex, - label - ); + segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); }); }; @@ -145,16 +117,34 @@ export default function PanelSegmentation({ }; const onSegmentColorClick = (segmentationId, segmentIndex) => { - // Todo: Implement color picker later - return; + const segmentation = segmentationService.getSegmentation(segmentationId); + + const segment = segmentation.segments[segmentIndex]; + const { color, opacity } = segment; + + const rgbaColor = { + r: color[0], + g: color[1], + b: color[2], + a: opacity / 255.0, + }; + + callColorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => { + if (actionId === 'cancel') { + return; + } + + segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [ + newRgbaColor.r, + newRgbaColor.g, + newRgbaColor.b, + newRgbaColor.a * 255.0, + ]); + }); }; const onSegmentDelete = (segmentationId, segmentIndex) => { - // segmentationService.removeSegmentFromSegmentation( - // segmentationId, - // segmentIndex - // ); - console.warn('not implemented yet'); + segmentationService.removeSegment(segmentationId, segmentIndex); }; const onToggleSegmentVisibility = (segmentationId, segmentIndex) => { @@ -174,6 +164,10 @@ export default function PanelSegmentation({ }); }; + const onToggleSegmentLock = (segmentationId, segmentIndex) => { + segmentationService.toggleSegmentLocked(segmentationId, segmentIndex); + }; + const onToggleSegmentationVisibility = segmentationId => { segmentationService.toggleSegmentationVisibility(segmentationId); }; @@ -188,47 +182,78 @@ export default function PanelSegmentation({ [segmentationService] ); + const onSegmentationDownload = segmentationId => { + commandsManager.runCommand('downloadSegmentation', { + segmentationId, + }); + }; + + const storeSegmentation = async segmentationId => { + const datasources = extensionManager.getActiveDataSource(); + + const displaySetInstanceUIDs = await createReportAsync({ + servicesManager, + getReport: () => + commandsManager.runCommand('storeSegmentation', { + segmentationId, + dataSource: datasources[0], + }), + reportType: 'Segmentation', + }); + + // Show the exported report in the active viewport as read only (similar to SR) + if (displaySetInstanceUIDs) { + // clear the segmentation that we exported, similar to the storeMeasurement + // where we remove the measurements and prompt again the user if they would like + // to re-read the measurements in a SR read only viewport + segmentationService.remove(segmentationId); + + viewportGridService.setDisplaySetsForViewport({ + viewportId: viewportGridService.getActiveViewportId(), + displaySetInstanceUIDs, + }); + } + }; + + const onSegmentationDownloadRTSS = segmentationId => { + commandsManager.runCommand('downloadRTSS', { + segmentationId, + }); + }; + return ( -
- {/* show segmentation table */} - {segmentations?.length ? ( + <> +
- _setSegmentationConfiguration( - selectedSegmentationId, - 'renderOutline', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value) } setOutlineOpacityActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineOpacity', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value) } setRenderFill={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderFill', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value) } setRenderInactiveSegmentations={value => _setSegmentationConfiguration( @@ -238,29 +263,17 @@ export default function PanelSegmentation({ ) } setOutlineWidthActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineWidthActive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value) } setFillAlpha={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlpha', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value) } setFillAlphaInactive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlphaInactive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value) } /> - ) : null} -
+
+ ); } diff --git a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx new file mode 100644 index 00000000000..f2d580cff46 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx @@ -0,0 +1,405 @@ +import React, { useCallback, useEffect, useState, useReducer } from 'react'; +import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui'; +import { Types } from '@ohif/extension-cornerstone'; +import { utilities } from '@cornerstonejs/tools'; + +const { segmentation: segmentationUtils } = utilities; + +const TOOL_TYPES = { + CIRCULAR_BRUSH: 'CircularBrush', + SPHERE_BRUSH: 'SphereBrush', + CIRCULAR_ERASER: 'CircularEraser', + SPHERE_ERASER: 'SphereEraser', + CIRCLE_SHAPE: 'CircleScissor', + RECTANGLE_SHAPE: 'RectangleScissor', + SPHERE_SHAPE: 'SphereScissor', + THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush', + THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush', +}; + +const ACTIONS = { + SET_TOOL_CONFIG: 'SET_TOOL_CONFIG', + SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL', +}; + +const initialState = { + Brush: { + brushSize: 15, + mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush' + }, + Eraser: { + brushSize: 15, + mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' + }, + Shapes: { + brushSize: 15, + mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' + }, + ThresholdBrush: { + brushSize: 15, + thresholdRange: [-500, 500], + }, + activeTool: null, +}; + +function toolboxReducer(state, action) { + switch (action.type) { + case ACTIONS.SET_TOOL_CONFIG: + const { tool, config } = action.payload; + return { + ...state, + [tool]: { + ...state[tool], + ...config, + }, + }; + case ACTIONS.SET_ACTIVE_TOOL: + return { ...state, activeTool: action.payload }; + default: + return state; + } +} + +function SegmentationToolbox({ servicesManager, extensionManager }) { + const { toolbarService, segmentationService, toolGroupService } = + servicesManager.services as Types.CornerstoneServices; + + const [viewportGrid] = useViewportGrid(); + const { viewports, activeViewportId } = viewportGrid; + + const [toolsEnabled, setToolsEnabled] = useState(false); + const [state, dispatch] = useReducer(toolboxReducer, initialState); + + const updateActiveTool = useCallback(() => { + if (!viewports?.size || activeViewportId === undefined) { + return; + } + const viewport = viewports.get(activeViewportId); + + if (!viewport) { + return; + } + + dispatch({ + type: ACTIONS.SET_ACTIVE_TOOL, + payload: toolGroupService.getActiveToolForViewport(viewport.viewportId), + }); + }, [activeViewportId, viewports, toolGroupService, dispatch]); + + const setToolActive = useCallback( + toolName => { + toolbarService.recordInteraction({ + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName, + }, + }, + ], + }); + + dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName }); + }, + [toolbarService, dispatch] + ); + + /** + * sets the tools enabled IF there are segmentations + */ + useEffect(() => { + const events = [ + segmentationService.EVENTS.SEGMENTATION_ADDED, + segmentationService.EVENTS.SEGMENTATION_UPDATED, + segmentationService.EVENTS.SEGMENTATION_REMOVED, + ]; + + const unsubscriptions = []; + + events.forEach(event => { + const { unsubscribe } = segmentationService.subscribe(event, () => { + const segmentations = segmentationService.getSegmentations(); + + const activeSegmentation = segmentations?.find(seg => seg.isActive); + + setToolsEnabled(activeSegmentation?.segmentCount > 0); + }); + + unsubscriptions.push(unsubscribe); + }); + + updateActiveTool(); + + return () => { + unsubscriptions.forEach(unsubscribe => unsubscribe()); + }; + }, [activeViewportId, viewports, segmentationService, updateActiveTool]); + + /** + * Update the active tool when the toolbar state changes + */ + useEffect(() => { + const { unsubscribe } = toolbarService.subscribe( + toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + () => { + updateActiveTool(); + } + ); + + return () => { + unsubscribe(); + }; + }, [toolbarService, updateActiveTool]); + + useEffect(() => { + // if the active tool is not a brush tool then do nothing + if (!Object.values(TOOL_TYPES).includes(state.activeTool)) { + return; + } + + // if the tool is Segmentation and it is enabled then do nothing + if (toolsEnabled) { + return; + } + + // if the tool is Segmentation and it is disabled, then switch + // back to the window level tool to not confuse the user when no + // segmentation is active or when there is no segment in the segmentation + setToolActive('WindowLevel'); + }, [toolsEnabled, state.activeTool, setToolActive]); + + const updateBrushSize = useCallback( + (toolName, brushSize) => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); + }); + }, + [toolGroupService] + ); + + const onBrushSizeChange = useCallback( + (valueAsStringOrNumber, toolCategory) => { + const value = Number(valueAsStringOrNumber); + + _getToolNamesFromCategory(toolCategory).forEach(toolName => { + updateBrushSize(toolName, value); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: toolCategory, + config: { brushSize: value }, + }, + }); + }, + [toolGroupService, dispatch] + ); + + const handleRangeChange = useCallback( + newRange => { + if ( + newRange[0] === state.ThresholdBrush.thresholdRange[0] && + newRange[1] === state.ThresholdBrush.thresholdRange[1] + ) { + return; + } + + const toolNames = _getToolNamesFromCategory('ThresholdBrush'); + + toolNames.forEach(toolName => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + const toolGroup = toolGroupService.getToolGroup(toolGroupId); + toolGroup.setToolConfiguration(toolName, { + strategySpecificConfiguration: { + THRESHOLD_INSIDE_CIRCLE: { + threshold: newRange, + }, + }, + }); + }); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: 'ThresholdBrush', + config: { thresholdRange: newRange }, + }, + }); + }, + [toolGroupService, dispatch, state.ThresholdBrush.thresholdRange] + ); + + return ( + setToolActive(TOOL_TYPES.CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'brush-radius', + type: 'range', + min: 0.5, + max: 99.5, + value: state.Brush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Brush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'brush-mode', + value: state.Brush.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Eraser', + icon: 'icon-tool-eraser', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCULAR_ERASER || + state.activeTool === TOOL_TYPES.SPHERE_ERASER, + onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER), + options: [ + { + name: 'Radius (mm)', + type: 'range', + id: 'eraser-radius', + min: 0.5, + max: 99.5, + value: state.Eraser.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Eraser'), + }, + { + name: 'Mode', + type: 'radio', + id: 'eraser-mode', + value: state.Eraser.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Shapes', + icon: 'icon-tool-shape', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCLE_SHAPE || + state.activeTool === TOOL_TYPES.RECTANGLE_SHAPE || + state.activeTool === TOOL_TYPES.SPHERE_SHAPE, + onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SHAPE), + options: [ + { + name: 'Mode', + type: 'radio', + value: state.Shapes.mode, + id: 'shape-mode', + values: [ + { value: TOOL_TYPES.CIRCLE_SHAPE, label: 'Circle' }, + { value: TOOL_TYPES.RECTANGLE_SHAPE, label: 'Rectangle' }, + { value: TOOL_TYPES.SPHERE_SHAPE, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Threshold Tool', + icon: 'icon-tool-threshold', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH || + state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, + onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'threshold-radius', + type: 'range', + min: 0.5, + max: 99.5, + value: state.ThresholdBrush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'ThresholdBrush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'threshold-mode', + value: state.activeTool, + values: [ + { value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + { + type: 'custom', + id: 'segmentation-threshold-range', + children: () => { + return ( +
+
+
Threshold
+ +
+ ); + }, + }, + ], + }, + ]} + /> + ); +} + +function _getToolNamesFromCategory(category) { + let toolNames = []; + switch (category) { + case 'Brush': + toolNames = ['CircularBrush', 'SphereBrush']; + break; + case 'Eraser': + toolNames = ['CircularEraser', 'SphereEraser']; + break; + case 'ThresholdBrush': + toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush']; + break; + default: + break; + } + + return toolNames; +} + +export default SegmentationToolbox; diff --git a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx index 69c30346975..6de2470d8bc 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Input, Dialog } from '@ohif/ui'; +import { Input, Dialog, ButtonEnums } from '@ohif/ui'; function callInputDialog(uiDialogService, label, callback) { const dialogId = 'enter-segment-label'; @@ -29,8 +29,8 @@ function callInputDialog(uiDialogService, label, callback) { noCloseButton: true, onClose: () => uiDialogService.dismiss({ id: dialogId }), actions: [ - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Confirm', type: 'secondary' }, + { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, + { id: 'save', text: 'Confirm', type: ButtonEnums.type.primary }, ], onSubmit: onSubmitHandler, body: ({ value, setValue }) => { @@ -39,7 +39,7 @@ function callInputDialog(uiDialogService, label, callback) { label="Enter the segment label" labelClassName="text-white text-[14px] leading-[1.2]" autoFocus - className="bg-black border-primary-main" + className="border-primary-main bg-black" type="text" value={value.label} onChange={event => { diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css new file mode 100644 index 00000000000..1c6bb206701 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css @@ -0,0 +1,3 @@ +.chrome-picker { + background: #090c29 !important; +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx new file mode 100644 index 00000000000..38e85efb29c --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Dialog } from '@ohif/ui'; +import { ChromePicker } from 'react-color'; + +import './colorPickerDialog.css'; + +function callColorPickerDialog(uiDialogService, rgbaColor, callback) { + const dialogId = 'pick-color'; + + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': + callback(value.rgbaColor, action.id); + break; + case 'cancel': + callback('', action.id); + break; + } + uiDialogService.dismiss({ id: dialogId }); + }; + + if (uiDialogService) { + uiDialogService.create({ + id: dialogId, + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Segment Color', + value: { rgbaColor }, + noCloseButton: true, + onClose: () => uiDialogService.dismiss({ id: dialogId }), + actions: [ + { id: 'cancel', text: 'Cancel', type: 'primary' }, + { id: 'save', text: 'Save', type: 'secondary' }, + ], + onSubmit: onSubmitHandler, + body: ({ value, setValue }) => { + const handleChange = color => { + setValue({ rgbaColor: color.rgb }); + }; + + return ( + + ); + }, + }, + }); + } +} + +export default callColorPickerDialog; diff --git a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts deleted file mode 100644 index f6b1522be7d..00000000000 --- a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts +++ /dev/null @@ -1,69 +0,0 @@ -async function _hydrateSEGDisplaySet({ - segDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = segDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForSEGDisplaySet( - segDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(segDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - segDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - return true; -} - -export default _hydrateSEGDisplaySet; diff --git a/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts new file mode 100644 index 00000000000..34ce1e54f11 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts @@ -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 }; diff --git a/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts new file mode 100644 index 00000000000..fa3c6d47c86 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts @@ -0,0 +1,190 @@ +import { Enums, cache } from '@cornerstonejs/core'; + +/** + * Updates the viewports in preparation for rendering segmentations. + * Evaluates each viewport to determine which need modifications, + * then for those viewports, changes them to a volume type and ensures + * they are ready for segmentation rendering. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport to be updated. + * @param params.loadFn - Function to load the segmentation data. + * @param params.servicesManager - The services manager. + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns Returns true upon successful update of viewports for segmentation rendering. + */ +async function updateViewportsForSegmentationRendering({ + viewportId, + loadFn, + servicesManager, + referencedDisplaySetInstanceUID, +}: { + viewportId: string; + loadFn: () => Promise; + servicesManager: any; + referencedDisplaySetInstanceUID?: string; +}) { + const { cornerstoneViewportService, segmentationService, viewportGridService } = + servicesManager.services; + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + referencedDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || viewport?.displaySetInstanceUIDs[0]; + + const updatedViewports = getUpdatedViewportsForSegmentation({ + servicesManager, + viewportId, + referencedDisplaySetInstanceUID, + }); + + // create Segmentation callback which needs to be waited until + // the volume is created (if coming from stack) + const createSegmentationForVolume = async () => { + const segmentationId = await loadFn(); + segmentationService.hydrateSegmentation(segmentationId); + }; + + // the reference volume that is used to draw the segmentation. so check if the + // volume exists in the cache (the target Viewport is already a volume viewport) + const volumeExists = Array.from(cache._volumeCache.keys()).some(volumeId => + volumeId.includes(referencedDisplaySetInstanceUID) + ); + + updatedViewports.forEach(async viewport => { + viewport.viewportOptions = { + ...viewport.viewportOptions, + viewportType: 'volume', + needsRerendering: true, + }; + const viewportId = viewport.viewportId; + + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const prevCamera = csViewport.getCamera(); + + // only run the createSegmentationForVolume for the targetViewportId + // since the rest will get handled by cornerstoneViewportService + if (volumeExists && viewportId === targetViewportId) { + await createSegmentationForVolume(); + return; + } + + const createNewSegmentationWhenVolumeMounts = async evt => { + const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac => + ac.uid.includes(referencedDisplaySetInstanceUID) + ); + + // Note: make sure to re-grab the viewport since it might have changed + // during the time it took for the volume to be mounted, for instance + // the stack viewport has been changed to a volume viewport + const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + volumeViewport.setCamera(prevCamera); + + volumeViewport.element.removeEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + + if (!isTheActiveViewportVolumeMounted) { + // it means it is one of those other updated viewports so just update the camera + return; + } + + if (viewportId === targetViewportId) { + await createSegmentationForVolume(); + } + }; + + csViewport.element.addEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + }); + + // Set the displaySets for the viewports that require to be updated + viewportGridService.setDisplaySetsForViewports(updatedViewports); + + return true; +} + +const getTargetViewport = ({ viewportId, viewportGridService }) => { + const { viewports, activeViewportId } = viewportGridService.getState(); + const targetViewportId = viewportId || activeViewportId; + + const viewport = viewports.get(targetViewportId); + + return viewport; +}; + +/** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ +function getUpdatedViewportsForSegmentation({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID, +}) { + const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } = + servicesManager.services; + + const { viewports } = viewportGridService.getState(); + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + const displaySetInstanceUIDs = viewports.get(targetViewportId).displaySetInstanceUIDs; + + const referenceDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || displaySetInstanceUIDs[0]; + + const referencedDisplaySet = displaySetService.getDisplaySetByUID(referenceDisplaySetInstanceUID); + const segmentationFrameOfReferenceUID = referencedDisplaySet.instances[0].FrameOfReferenceUID; + + const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( + targetViewportId, + referenceDisplaySetInstanceUID + ); + + viewports.forEach((viewport, viewportId) => { + if ( + targetViewportId === viewportId || + updatedViewports.find(v => v.viewportId === viewportId) + ) { + return; + } + + const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( + viewport.displaySetInstanceUIDs, + segmentationFrameOfReferenceUID + ); + + if (shouldDisplaySeg) { + updatedViewports.push({ + viewportId, + displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, + viewportOptions: { + viewportType: 'volume', + needsRerendering: true, + }, + }); + } + }); + return updatedViewports; +} + +export { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +}; diff --git a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts index e032542f133..8ddc088c6c8 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts @@ -1,12 +1,7 @@ -function createSEGToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createSEGToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index c0b9385f130..9f8c1dddf78 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -1,4 +1,4 @@ -import hydrateSEGDisplaySet from './_hydrateSEG'; +import { ButtonEnums } from '@ohif/ui'; const RESPONSE = { NO_NEVER: -1, @@ -9,21 +9,23 @@ const RESPONSE = { function promptHydrateSEG({ servicesManager, segDisplaySet, - viewportIndex, + viewportId, + preHydrateCallbacks, + hydrateSEGDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { + preHydrateCallbacks?.forEach(callback => { + callback(); + }); + const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); resolve(isHydrated); @@ -31,17 +33,17 @@ function promptHydrateSEG({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { - type: 'secondary', + type: ButtonEnums.type.secondary, text: 'No', value: RESPONSE.CANCEL, }, { - type: 'primary', + type: ButtonEnums.type.primary, text: 'Yes', value: RESPONSE.HYDRATE_SEG, }, @@ -52,7 +54,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index 08ed004f077..fb39f8c36fe 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -2,14 +2,9 @@ import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import OHIF, { utils } from '@ohif/core'; -import { - LoadingIndicatorTotalPercent, - useViewportGrid, - ViewportActionBar, -} from '@ohif/ui'; +import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; -import hydrateSEGDisplaySet from '../utils/_hydrateSEG'; import _getStatusComponent from './_getStatusComponent'; const { formatDate } = utils; @@ -20,13 +15,14 @@ function OHIFCornerstoneSEGViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, + commandsManager, } = props; const { t } = useTranslation('SEGViewport'); + const viewportId = viewportOptions.viewportId; const { displaySetService, @@ -36,7 +32,7 @@ function OHIFCornerstoneSEGViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`; // SEG viewport will always have a single display set if (displaySets.length > 1) { @@ -48,7 +44,6 @@ function OHIFCornerstoneSEGViewport(props) { const [viewportGrid, viewportGridService] = useViewportGrid(); // States - const [isToolGroupCreated, setToolGroupCreated] = useState(false); const [selectedSegment, setSelectedSegment] = useState(1); // Hydration means that the SEG is opened and segments are loaded into the @@ -67,11 +62,12 @@ function OHIFCornerstoneSEGViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet(); const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( - referencedDisplaySet + referencedDisplaySet, + segDisplaySet ); referencedDisplaySetRef.current = { @@ -92,14 +88,20 @@ function OHIFCornerstoneSEGViewport(props) { setElement(null); }; + const storePresentationState = useCallback(() => { + viewportGrid?.viewports.forEach(({ viewportId }) => { + commandsManager.runCommand('storePresentation', { + viewportId, + }); + }); + }, [viewportGrid]); + const getCornerstoneViewport = useCallback(() => { const { component: Component } = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -117,7 +119,7 @@ function OHIFCornerstoneSEGViewport(props) { // initialImageIndex={initialImageIndex} > ); - }, [viewportIndex, segDisplaySet, toolGroupId]); + }, [viewportId, segDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -139,11 +141,7 @@ function OHIFCornerstoneSEGViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -156,31 +154,29 @@ function OHIFCornerstoneSEGViewport(props) { promptHydrateSEG({ servicesManager, - viewportIndex, + viewportId, segDisplaySet, + preHydrateCallbacks: [storePresentationState], + hydrateSEGDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]); + }, [servicesManager, viewportId, segDisplaySet, segIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.segDisplaySet.displaySetInstanceUID === - segDisplaySet.displaySetInstanceUID - ) { + if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) { setSegIsLoading(false); } if (evt.overlappingSegments) { uiNotificationService.show({ title: 'Overlapping Segments', - message: - 'Overlapping segments detected which is not currently supported', + message: 'Overlapping segments detected which is not currently supported', type: 'warning', }); } @@ -215,12 +211,10 @@ function OHIFCornerstoneSEGViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -241,19 +235,11 @@ function OHIFCornerstoneSEGViewport(props) { // This creates a custom tool group which has the lifetime of this view // only, and does NOT interfere with currently displayed segmentations. - toolGroup = createSEGToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); - - setToolGroupCreated(true); + toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); // Only destroy the viewport specific implementation toolGroupService.destroyToolGroup(toolGroupId); @@ -265,9 +251,7 @@ function OHIFCornerstoneSEGViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [segDisplaySet]); @@ -288,7 +272,7 @@ function OHIFCornerstoneSEGViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -307,16 +291,28 @@ function OHIFCornerstoneSEGViewport(props) { SpacingBetweenSlices, } = referencedDisplaySetRef.current.metadata; + const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [segDisplaySet], + viewportId, + }); + }; + const onStatusClick = async () => { + // Before hydrating a SEG and make it added to all viewports in the grid + // that share the same frameOfReferenceUID, we need to store the viewport grid + // presentation state, so that we can restore it after hydrating the SEG. This is + // required if the user has changed the viewport (other viewport than SEG viewport) + // presentation state (w/l and invert) and then opens the SEG. If we don't store + // the presentation state, the viewport will be reset to the default presentation + storePresentationState(); const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); }; - return ( <> -
+
{segIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; - case false: - StatusIcon = () => ; + case false: + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load segmentation.
; } const StatusArea = () => ( -
-
+
+
SEG
{!isHydrated && (
@@ -42,11 +44,13 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
); - return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js index 76dcd9ecfaf..9e07dd8ac16 100644 --- a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js @@ -41,13 +41,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-sr/CHANGELOG.md b/extensions/cornerstone-dicom-sr/CHANGELOG.md new file mode 100644 index 00000000000..e7bfa3bddb4 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/CHANGELOG.md @@ -0,0 +1,452 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 260f898c3a8..160139629c7 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-sr", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", @@ -32,10 +32,10 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0", - "@ohif/ui": "3.6.0", + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-measurement-tracking": "3.7.0-beta.108", + "@ohif/ui": "3.7.0-beta.108", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", @@ -44,9 +44,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.1.0", - "@cornerstonejs/core": "^1.1.0", - "@cornerstonejs/tools": "^1.1.0", + "@cornerstonejs/adapters": "^1.20.3", + "@cornerstonejs/core": "^1.20.3", + "@cornerstonejs/tools": "^1.20.3", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js index f8c9459e4dc..ec68cabb951 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.js +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -17,11 +17,7 @@ const { log } = OHIF; * @param options Naturalized DICOM JSON headers to merge into the displaySet. * */ -const _generateReport = ( - measurementData, - additionalFindingTypes, - options = {} -) => { +const _generateReport = (measurementData, additionalFindingTypes, options = {}) => { const filteredToolState = getFilteredCornerstoneToolState( measurementData, additionalFindingTypes @@ -54,16 +50,8 @@ const commandsModule = ({}) => { * as opposed to Finding Sites. * that you wish to serialize. */ - downloadReport: ({ - measurementData, - additionalFindingTypes, - options = {}, - }) => { - const srDataset = actions.generateReport( - measurementData, - additionalFindingTypes, - options - ); + downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => { + const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options); const reportBlob = dcmjs.data.datasetToBlob(srDataset); //Create a URL for the binary. @@ -91,28 +79,19 @@ const commandsModule = ({}) => { log.info('[DICOMSR] storeMeasurements'); if (!dataSource || !dataSource.store || !dataSource.store.dicom) { - log.error( - '[DICOMSR] datasource has no dataSource.store.dicom endpoint!' - ); + log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!'); return Promise.reject({}); } try { - const naturalizedReport = _generateReport( - measurementData, - additionalFindingTypes, - options - ); + const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options); const { StudyInstanceUID, ContentSequence } = naturalizedReport; // The content sequence has 5 or more elements, of which // the `[4]` element contains the annotation data, so this is // checking that there is some annotation data present. if (!ContentSequence?.[4].ContentSequence?.length) { - console.log( - 'naturalizedReport missing imaging content', - naturalizedReport - ); + console.log('naturalizedReport missing imaging content', naturalizedReport); throw new Error('Invalid report, no content'); } @@ -130,12 +109,8 @@ const commandsModule = ({}) => { return naturalizedReport; } catch (error) { console.warn(error); - log.error( - `[DICOMSR] Error while saving the measurements: ${error.message}` - ); - throw new Error( - error.message || 'Error while saving the measurements.' - ); + log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`); + throw new Error(error.message || 'Error while saving the measurements.'); } }, }; diff --git a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts index e9fcf31be59..8d47aca0c58 100644 --- a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const srProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'SR Key Images', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index e0209447127..5cdf455f605 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -29,9 +29,7 @@ const validateSameStudyUID = (uid: string, instances): void => { instances.forEach(it => { if (it.StudyInstanceUID !== uid) { console.warn('Not all instances have the same UID', uid, it); - throw new Error( - `Instances ${it.SOPInstanceUID} does not belong to ${uid}` - ); + throw new Error(`Instances ${it.SOPInstanceUID} does not belong to ${uid}`); } }); }; @@ -51,10 +49,7 @@ const CodeNameCodeSequenceValues = { const CodingSchemeDesignators = { SRT: 'SRT', - CornerstoneCodeSchemes: [ - Cornerstone3DCodeScheme.CodingSchemeDesignator, - 'CST4', - ], + CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'], }; const RELATIONSHIP_TYPE = { @@ -71,10 +66,7 @@ const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; * @param instances is a list of instances from THIS series that are not * in this DICOM SR Display Set already. */ -function addInstances( - instances: InstanceMetadata[], - displaySetService: DisplaySetService -) { +function addInstances(instances: InstanceMetadata[], displaySetService: DisplaySetService) { this.instances.push(...instances); utils.sortStudyInstances(this.instances); // The last instance is the newest one, so is the one most interesting. @@ -93,11 +85,7 @@ function addInstances( * @param servicesManager is the services that can be used for creating * @returns The list of display sets created for the given instances object */ -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { // If the series has no instances, stop here if (!instances || !instances.length) { throw new Error('No instances were provided'); @@ -123,8 +111,7 @@ function _getDisplaySetsFromSeries( if ( !ConceptNameCodeSequence || - ConceptNameCodeSequence.CodeValue !== - CodeNameCodeSequenceValues.ImagingMeasurementReport + ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport ) { servicesManager.services.uiNotificationService.show({ title: 'DICOM SR', @@ -184,36 +171,21 @@ function _load(displaySet, servicesManager, extensionManager) { // Check currently added displaySets and add measurements if the sources exist. displaySetService.activeDisplaySets.forEach(activeDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet( - displaySet, - activeDisplaySet, - dataSource - ); + _checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource); }); // Subscribe to new displaySets as the source may come in after. - displaySetService.subscribe( - displaySetService.EVENTS.DISPLAY_SETS_ADDED, - data => { - const { displaySetsAdded } = data; - // If there are still some measurements that have not yet been loaded into cornerstone, - // See if we can load them onto any of the new displaySets. - displaySetsAdded.forEach(newDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet( - displaySet, - newDisplaySet, - dataSource - ); - }); - } - ); + displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => { + const { displaySetsAdded } = data; + // If there are still some measurements that have not yet been loaded into cornerstone, + // See if we can load them onto any of the new displaySets. + displaySetsAdded.forEach(newDisplaySet => { + _checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource); + }); + }); } -function _checkIfCanAddMeasurementsToDisplaySet( - srDisplaySet, - newDisplaySet, - dataSource -) { +function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) { let unloadedMeasurements = srDisplaySet.measurements.filter( measurement => measurement.loaded === false ); @@ -248,8 +220,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( const { coords } = measurement; coords.forEach(coord => { - const SOPInstanceUID = - coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; + const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; if (!SOPInstanceUIDs.includes(SOPInstanceUID)) { SOPInstanceUIDs.push(SOPInstanceUID); @@ -257,9 +228,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( }); }); - const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet( - newDisplaySet - ); + const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet); for (const imageId of imageIdsForDisplaySet) { if (!unloadedMeasurements.length) { @@ -267,25 +236,13 @@ function _checkIfCanAddMeasurementsToDisplaySet( return; } - const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID( - imageId - ); + const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId); if (SOPInstanceUIDs.includes(SOPInstanceUID)) { for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { const measurement = unloadedMeasurements[j]; - if ( - _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber - ) - ) { - addMeasurement( - measurement, - imageId, - newDisplaySet.displaySetInstanceUID - ); + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) { + addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID); unloadedMeasurements.splice(j, 1); } @@ -294,11 +251,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber -) { +function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) { const { coords } = measurement; // NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM @@ -308,8 +261,9 @@ function _measurementReferencesSOPInstanceUID( measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || 1; - if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) + if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) { return false; + } for (let j = 0; j < coords.length; j++) { const coord = coords[j]; @@ -323,11 +277,7 @@ function _measurementReferencesSOPInstanceUID( function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ @@ -342,30 +292,22 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { function _getMeasurements(ImagingMeasurementReportContentSequence) { const ImagingMeasurements = ImagingMeasurementReportContentSequence.find( item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImagingMeasurements + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements ); - const MeasurementGroups = _getSequenceAsArray( - ImagingMeasurements.ContentSequence - ).filter( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.MeasurementGroup + const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup ); - const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups - ); + const mergedContentSequencesByTrackingUniqueIdentifiers = + _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups); const measurements = []; Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach( trackingUniqueIdentifier => { const mergedContentSequence = - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier]; const measurement = _processMeasurement(mergedContentSequence); @@ -378,15 +320,11 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) { return measurements; } -function _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups -) { +function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) { const mergedContentSequencesByTrackingUniqueIdentifiers = {}; MeasurementGroups.forEach(MeasurementGroup => { - const ContentSequence = _getSequenceAsArray( - MeasurementGroup.ContentSequence - ); + const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence); const TrackingUniqueIdentifierItem = ContentSequence.find( item => @@ -395,22 +333,16 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( ); if (!TrackingUniqueIdentifierItem) { - console.warn( - 'No Tracking Unique Identifier, skipping ambiguous measurement.' - ); + console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.'); } const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID; - if ( - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] === undefined - ) { + if (mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] === undefined) { // Add the full ContentSequence - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] = [...ContentSequence]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] = [ + ...ContentSequence, + ]; } else { // Add the ContentSequence minus the tracking identifier, as we have this // Information in the merged ContentSequence anyway. @@ -419,9 +351,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( item.ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.TrackingUniqueIdentifier ) { - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ].push(item); + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item); } }); } @@ -446,18 +376,12 @@ function _processTID1410Measurement(mergedContentSequence) { // Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level, // And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D - const graphicItem = mergedContentSequence.find( - group => group.ValueType === 'SCOORD' - ); + const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); if (!graphicItem) { @@ -467,9 +391,7 @@ function _processTID1410Measurement(mergedContentSequence) { return; } - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); const measurement = { loaded: false, @@ -484,10 +406,7 @@ function _processTID1410Measurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -496,32 +415,22 @@ function _processTID1410Measurement(mergedContentSequence) { } function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); const finding = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.Finding + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding ); const findingSites = mergedContentSequence.filter( item => - item.ConceptNameCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.SRT && - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.FindingSite + item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite ); const measurement = { @@ -537,8 +446,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( finding.ConceptCodeSequence.CodingSchemeDesignator ) && - finding.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ) { measurement.labels.push({ label: CORNERSTONE_FREETEXT_CODE_VALUE, @@ -553,8 +461,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( FindingSite.ConceptCodeSequence.CodingSchemeDesignator ) && - FindingSite.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ); if (cornerstoneFreeTextFindingSite) { @@ -566,18 +473,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { } NUMContentItems.forEach(item => { - const { - ConceptNameCodeSequence, - ContentSequence, - MeasuredValueSequence, - } = item; + const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item; const { ValueType } = ContentSequence; if (!ValueType === 'SCOORD') { - console.warn( - `Graphic ${ValueType} not currently supported, skipping annotation.` - ); + console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`); return; } @@ -590,10 +491,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -633,17 +531,12 @@ function _getCoordsFromSCOORDOrSCOORD3D(item) { return coords; } -function _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence -) { +function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) { const { CodeMeaning } = ConceptNameCodeSequence; const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence; const { CodeValue } = MeasurementUnitsCodeSequence; - const formatedNumericValue = NumericValue - ? Number(NumericValue).toFixed(2) - : ''; + const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : ''; return { label: CodeMeaning, @@ -653,24 +546,20 @@ function _getLabelFromMeasuredValueSequence( function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { const ImageLibrary = ImagingMeasurementReportContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibrary + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary ); - const ImageLibraryGroup = _getSequenceAsArray( - ImageLibrary.ContentSequence - ).find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibraryGroup + const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup ); const referencedImages = []; _getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { const { ReferencedSOPSequence } = item; - if (!ReferencedSOPSequence) return; + if (!ReferencedSOPSequence) { + return; + } for (const ref of _getSequenceAsArray(ReferencedSOPSequence)) { if (ref.ReferencedSOPClassUID) { const { ReferencedSOPClassUID, ReferencedSOPInstanceUID } = ref; @@ -687,7 +576,9 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { } function _getSequenceAsArray(sequence) { - if (!sequence) return []; + if (!sequence) { + return []; + } return Array.isArray(sequence) ? sequence : [sequence]; } diff --git a/extensions/cornerstone-dicom-sr/src/index.tsx b/extensions/cornerstone-dicom-sr/src/index.tsx index 9b87ea98cbf..9b0700481ed 100644 --- a/extensions/cornerstone-dicom-sr/src/index.tsx +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -1,8 +1,6 @@ import React from 'react'; import getSopClassHandlerModule from './getSopClassHandlerModule'; -import getHangingProtocolModule, { - srProtocol, -} from './getHangingProtocolModule'; +import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule'; import onModeEnter from './onModeEnter'; import getCommandsModule from './commandsModule'; import preRegistration from './init'; @@ -12,9 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport'; import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport'); }); const OHIFCornerstoneSRViewport = props => { @@ -58,7 +54,7 @@ const dicomSRExtension = { }, getCommandsModule, getSopClassHandlerModule, - // Include dynmically computed values such as toolNames not known till instantiation + // Include dynamically computed values such as toolNames not known till instantiation getUtilityModule({ servicesManager }) { return [ { diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index 84cbcaf37f9..1ab3300d778 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -18,9 +18,7 @@ import toolNames from './tools/toolNames'; /** * @param {object} configuration */ -export default function init({ - configuration = {}, -}: Types.Extensions.ExtensionParams): void { +export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void { addTool(DICOMSRDisplayTool); addToolInstance(toolNames.SRLength, LengthTool, {}); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts index 347159d206b..8160ea6501a 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -40,49 +40,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool { isPointNearTool = () => null; getHandleNearImagePoint = () => null; - renderAnnotation = ( - enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => { const { viewport } = enabledElement; const { element } = viewport; - let annotations = annotation.state.getAnnotations( - this.getToolName(), - element - ); + let annotations = annotation.state.getAnnotations(this.getToolName(), element); // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { return; } - annotations = this.filterInteractableAnnotationsForElement( - element, - annotations - ); + annotations = this.filterInteractableAnnotationsForElement(element, annotations); if (!annotations?.length) { return; } - const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement( - element - ); + const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement(element); - const { - activeIndex, - trackingUniqueIdentifiers, - } = trackingUniqueIdentifiersForElement; + const { activeIndex, trackingUniqueIdentifiers } = trackingUniqueIdentifiersForElement; - const activeTrackingUniqueIdentifier = - trackingUniqueIdentifiers[activeIndex]; + const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex]; // Filter toolData to only render the data for the active SR. const filteredAnnotations = annotations.filter(annotation => - trackingUniqueIdentifiers.includes( - annotation.data?.cachedStats?.TrackingUniqueIdentifier - ) + trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier) ); if (!viewport._actors?.size) { @@ -138,8 +121,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { break; case SCOORD_TYPES.ELLIPSE: renderMethod = this.renderEllipse; - canvasCoordinatesAdapter = - utilities.math.ellipse.getCanvasEllipseCorners; + canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners; break; default: throw new Error(`Unsupported GraphicType: ${GraphicType}`); @@ -221,15 +203,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool { renderableData.map((data, index) => { canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); const handleGroupUID = '0'; - drawing.drawHandles( - svgDrawingHelper, - annotationUID, - handleGroupUID, - canvasCoordinates, - { - color: options.color, - } - ); + drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, { + color: options.color, + }); }); } @@ -248,10 +224,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { canvasCoordinates.push(viewport.worldToCanvas(point)); // We get the other point for the arrow by using the image size - const imagePixelModule = metaData.get( - 'imagePixelModule', - referencedImageId - ); + const imagePixelModule = metaData.get('imagePixelModule', referencedImageId); let xOffset = 10; let yOffset = 10; @@ -309,23 +282,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const rotation = viewport.getRotation(); - canvasCoordinates = ellipsePointsWorld.map(p => - viewport.worldToCanvas(p) - ); + canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p)); let canvasCorners; if (rotation == 90 || rotation == 270) { - canvasCorners = >( - utilities.math.ellipse.getCanvasEllipseCorners([ - canvasCoordinates[2], - canvasCoordinates[3], - canvasCoordinates[0], - canvasCoordinates[1], - ]) - ); + canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners([ + canvasCoordinates[2], + canvasCoordinates[3], + canvasCoordinates[0], + canvasCoordinates[1], + ]) as Array; } else { - canvasCorners = >( - utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates) - ); + canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners( + canvasCoordinates + ) as Array; } const lineUID = `${index}`; @@ -368,23 +337,14 @@ export default class DICOMSRDisplayTool extends AnnotationTool { adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates); } const textLines = this._getTextBoxLinesFromLabels(label); - const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas( - adaptedCanvasCoordinates - ); + const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates); - annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld( - canvasTextBoxCoords - ); + annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); - const textBoxPosition = viewport.worldToCanvas( - annotation.data.handles.textBox.worldPosition - ); + const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition); const textBoxUID = '1'; - const textBoxOptions = this.getLinkedTextBoxStyle( - styleSpecifier, - annotation - ); + const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation); const boundingBox = drawing.drawLinkedTextBox( svgDrawingHelper, diff --git a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js index f359e3d2737..5636e6563d2 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js +++ b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js @@ -27,15 +27,11 @@ function setTrackingUniqueIdentifiersForElement( }; } -function setActiveTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier -) { +function setActiveTrackingUniqueIdentifierForElement(element, TrackingUniqueIdentifier) { const enabledElement = getEnabledElement(element); const { viewport } = enabledElement; - const trackingIdentifiersForElement = - state.trackingIdentifiersByViewportId[viewport.id]; + const trackingIdentifiersForElement = state.trackingIdentifiersByViewportId[viewport.id]; if (trackingIdentifiersForElement) { const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts index ac8e43003fa..e78b3c7f15c 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts @@ -8,11 +8,7 @@ const EPSILON = 1e-4; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; -export default function addMeasurement( - measurement, - imageId, - displaySetInstanceUID -) { +export default function addMeasurement(measurement, imageId, displaySetInstanceUID) { // TODO -> Render rotated ellipse . const toolName = toolNames.DICOMSRDisplay; @@ -31,12 +27,7 @@ export default function addMeasurement( } measurementData.renderableData[GraphicType].push( - _getRenderableData( - GraphicType, - GraphicData, - imageId, - measurement.TrackingIdentifier - ) + _getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier) ); }); @@ -86,12 +77,7 @@ export default function addMeasurement( delete measurement.coords; } -function _getRenderableData( - GraphicType, - GraphicData, - imageId, - TrackingIdentifier -) { +function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) { const [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); let renderableData: csTypes.Point3[]; @@ -207,38 +193,22 @@ function _getRenderableData( throw new Error('imageId does not have imagePlaneModule metadata'); } - const { - columnCosines, - }: { columnCosines: csTypes.Point3 } = imagePlaneModule; + const { columnCosines }: { columnCosines: csTypes.Point3 } = imagePlaneModule; // find which axis is parallel to the columnCosines const columnCosinesVec = vec3.fromValues(...columnCosines); - const projectedMajorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, majorAxisVec) - ); - const projectedMinorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, minorAxisVec) - ); + const projectedMajorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, majorAxisVec)); + const projectedMinorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, minorAxisVec)); const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec); const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec); renderableData = []; if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[0], - pointsWorld[1], - pointsWorld[2], - pointsWorld[3], - ]; + renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]]; } else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[2], - pointsWorld[3], - pointsWorld[0], - pointsWorld[1], - ]; + renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]]; } else { console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED'); } diff --git a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts index 889926d812b..bd4cf2d1e95 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts @@ -1,10 +1,6 @@ import { addTool } from '@cornerstonejs/tools'; -export default function addToolInstance( - name: string, - toolClass, - configuration? -): void { +export default function addToolInstance(name: string, toolClass, configuration?): void { class InstanceClass extends toolClass { static toolName = name; } diff --git a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts index a1dd42d2c23..72416416510 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts @@ -3,12 +3,11 @@ import { DisplaySetService, classes } from '@ohif/core'; const ImageSet = classes.ImageSet; const findInstance = (measurement, displaySetService: DisplaySetService) => { - const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = - measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - if (!referencedDisplaySet.images) return; + const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = measurement; + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (!referencedDisplaySet.images) { + return; + } return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid); }; @@ -16,16 +15,17 @@ const findInstance = (measurement, displaySetService: DisplaySetService) => { * contained within the provided display set. * @return an array of instances referenced. */ -const findReferencedInstances = ( - displaySetService: DisplaySetService, - displaySet -) => { +const findReferencedInstances = (displaySetService: DisplaySetService, displaySet) => { const instances = []; const instanceById = {}; for (const measurement of displaySet.measurements) { const { imageId } = measurement; - if (!imageId) continue; - if (instanceById[imageId]) continue; + if (!imageId) { + continue; + } + if (instanceById[imageId]) { + continue; + } const instance = findInstance(measurement, displaySetService); if (!instance) { @@ -50,7 +50,7 @@ const findReferencedInstances = ( const createReferencedImageDisplaySet = (displaySetService, displaySet) => { const instances = findReferencedInstances(displaySetService, displaySet); // This will be a member function of the created image set - const updateInstances = function() { + const updateInstances = function () { this.images.splice( 0, this.images.length, diff --git a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js index 99ffbe0b540..30e68157655 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js @@ -9,11 +9,12 @@ const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => { let instanceFound; displaySets.find(displaySet => { - if (!displaySet.images) return false; + if (!displaySet.images) { + return false; + } instanceFound = displaySet.images.find( - instanceMetadata => - instanceMetadata.getSOPInstanceUID() === SOPInstanceUID + instanceMetadata => instanceMetadata.getSOPInstanceUID() === SOPInstanceUID ); return !!instanceFound; diff --git a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js index 9f8a3129ada..138d72df983 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js @@ -18,10 +18,7 @@ const findMostRecentStructuredReport = studies => { } if (isStructuredReportSeries(series)) { - if ( - !mostRecentStructuredReport || - compareSeriesDate(series, mostRecentStructuredReport) - ) { + if (!mostRecentStructuredReport || compareSeriesDate(series, mostRecentStructuredReport)) { mostRecentStructuredReport = series; } } @@ -38,10 +35,7 @@ const findMostRecentStructuredReport = studies => { * @returns {boolean} */ const isStructuredReportSeries = series => { - const supportedSopClassUIDs = [ - '1.2.840.10008.5.1.4.1.1.88.22', - '1.2.840.10008.5.1.4.1.1.11.1', - ]; + const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22', '1.2.840.10008.5.1.4.1.1.11.1']; const firstInstance = series.getFirstInstance(); const SOPClassUID = firstInstance.getData().metadata.SOPClassUID; @@ -50,7 +44,7 @@ const isStructuredReportSeries = series => { }; /** - * Checkes if series1 is newer than series2 + * Checks if series1 is newer than series2 * * @param {Object} series1 - Series Metadata 1 * @param {Object} series2 - Series Metadata 2 diff --git a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts index 18c5b8465eb..2c28311d57c 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts @@ -2,17 +2,12 @@ import OHIF from '@ohif/core'; import { annotation } from '@cornerstonejs/tools'; const { log } = OHIF; -function getFilteredCornerstoneToolState( - measurementData, - additionalFindingTypes -) { +function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes) { const filteredToolState = {}; function addToFilteredToolState(annotation, toolType) { if (!annotation.metadata?.referencedImageId) { - log.warn( - `[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}` - ); + log.warn(`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`); return; } @@ -30,9 +25,7 @@ function getFilteredCornerstoneToolState( }; } - const measurementDataI = measurementData.find( - md => md.uid === annotation.annotationUID - ); + const measurementDataI = measurementData.find(md => md.uid === annotation.annotationUID); const toolData = imageIdSpecificToolState[toolType].data; let { finding } = measurementDataI; @@ -77,9 +70,7 @@ function getFilteredCornerstoneToolState( for (let i = 0; i < framesOfReference.length; i++) { const frameOfReference = framesOfReference[i]; - const frameOfReferenceAnnotations = annotationManager.getAnnotations( - frameOfReference - ); + const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference); const toolTypes = Object.keys(frameOfReferenceAnnotations); @@ -91,9 +82,7 @@ function getFilteredCornerstoneToolState( if (annotations) { for (let k = 0; k < annotations.length; k++) { const annotation = annotations[k]; - const uidIndex = uids.findIndex( - uid => uid === annotation.annotationUID - ); + const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID); if (uidIndex !== -1) { addToFilteredToolState(annotation, toolType); diff --git a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js index 18a2ad1f5cc..1fa18ee9c51 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js +++ b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js @@ -9,9 +9,7 @@ export default function getLabelFromDCMJSImportedToolData(toolData) { const { findingSites = [], finding } = toolData; - let freeTextLabel = findingSites.find( - fs => fs.CodeValue === 'CORNERSTONEFREETEXT' - ); + let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT'); if (freeTextLabel) { return freeTextLabel.CodeMeaning; diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js index 346bfc5db10..17216a84422 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js @@ -2,6 +2,8 @@ import { utilities, metaData } from '@cornerstonejs/core'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData'; import { adaptersSR } from '@cornerstonejs/adapters'; +import { annotation as CsAnnotation } from '@cornerstonejs/tools'; +const { locking } = CsAnnotation; const { guid } = OHIF.utils; const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D; @@ -12,20 +14,26 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const convertCode = (codingValues, code) => { - if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return; + if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') { + return; + } const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`; const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning }; return ret; }; const convertSites = (codingValues, sites) => { - if (!sites || !sites.length) return; + if (!sites || !sites.length) { + return; + } const ret = []; // Do as a loop to convert away from Proxy instances for (let i = 0; i < sites.length; i++) { // Deal with irregular conversion from dcmjs const site = convertCode(codingValues, sites[i][0] || sites[i]); - if (site) ret.push(site); + if (site) { + ret.push(site); + } } return (ret.length && ret) || undefined; }; @@ -35,23 +43,16 @@ const convertSites = (codingValues, sites) => { * */ export default function hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ) { + const annotationManager = CsAnnotation.state.getAnnotationManager(); + const disableEditing = appConfig?.disableEditing; const dataSource = extensionManager.getActiveDataSource()[0]; - const { - measurementService, - displaySetService, - customizationService, - } = servicesManager.services; - - const codingValues = customizationService.getCustomization( - 'codingValues', - {} - ); - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const { measurementService, displaySetService, customizationService } = servicesManager.services; + + const codingValues = customizationService.getCustomization('codingValues', {}); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // TODO -> We should define a strict versioning somewhere. const mappings = measurementService.getSourceMappings( @@ -115,16 +116,14 @@ export default function hydrateStructuredReport( // TODO: notification if no hydratable? Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -140,10 +139,7 @@ export default function hydrateStructuredReport( for (let i = 0; i < imageIds.length; i++) { const imageId = imageIds[i]; - const { SeriesInstanceUID, StudyInstanceUID } = metaData.get( - 'instance', - imageId - ); + const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId); if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) { SeriesInstanceUIDs.push(SeriesInstanceUID); @@ -152,23 +148,19 @@ export default function hydrateStructuredReport( if (!targetStudyInstanceUID) { targetStudyInstanceUID = StudyInstanceUID; } else if (targetStudyInstanceUID !== StudyInstanceUID) { - console.warn( - 'NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.' - ); + console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.'); } } Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -198,21 +190,13 @@ export default function hydrateStructuredReport( CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); annotation.data.label = getLabelFromDCMJSImportedToolData(toolData); - annotation.data.finding = convertCode( - codingValues, - toolData.finding?.[0] - ); - annotation.data.findingSites = convertSites( - codingValues, - toolData.findingSites - ); + annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]); + annotation.data.findingSites = convertSites(codingValues, toolData.findingSites); annotation.data.site = annotation.data.findingSites?.[0]; - const matchingMapping = mappings.find( - m => m.annotationType === annotationType - ); + const matchingMapping = mappings.find(m => m.annotationType === annotationType); - measurementService.addRawMeasurement( + const newAnnotationUID = measurementService.addRawMeasurement( source, annotationType, { annotation }, @@ -220,6 +204,11 @@ export default function hydrateStructuredReport( dataSource ); + if (disableEditing) { + const addedAnnotation = annotationManager.getAnnotation(newAnnotationUID); + locking.setAnnotationLocked(addedAnnotation, true); + } + if (!imageIds.includes(imageId)) { imageIds.push(imageId); } @@ -245,15 +234,14 @@ function _mapLegacyDataSet(dataset) { ); // Retrieve the Measurements themselves - const measurementGroups = toArray( - imagingMeasurementContent.ContentSequence - ).filter(codeMeaningEquals(GROUP)); + const measurementGroups = toArray(imagingMeasurementContent.ContentSequence).filter( + codeMeaningEquals(GROUP) + ); // For each of the supported measurement types, compute the measurement data const measurementData = {}; - const cornerstoneToolClasses = - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + const cornerstoneToolClasses = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const registeredToolClasses = []; @@ -263,13 +251,10 @@ function _mapLegacyDataSet(dataset) { }); measurementGroups.forEach((measurementGroup, index) => { - const measurementGroupContentSequence = toArray( - measurementGroup.ContentSequence - ); + const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence); const TrackingIdentifierGroup = measurementGroupContentSequence.find( - contentItem => - contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER + contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER ); const TrackingIdentifier = TrackingIdentifierGroup.TextValue; diff --git a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js index 41602dd257f..d6f6a748413 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js @@ -1,8 +1,7 @@ import { adaptersSR } from '@cornerstonejs/adapters'; const cornerstoneAdapters = - adaptersSR.Cornerstone3D.MeasurementReport - .CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG; @@ -24,8 +23,7 @@ export default function isRehydratable(displaySet, mappings) { const adapterKeys = Object.keys(cornerstoneAdapters).filter( adapterKey => - typeof cornerstoneAdapters[adapterKey] - .isValidCornerstoneTrackingIdentifier === 'function' + typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function' ); const adapters = []; @@ -48,19 +46,13 @@ export default function isRehydratable(displaySet, mappings) { const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`; - return adapter.isValidCornerstoneTrackingIdentifier( - mappedTrackingIdentifier - ); + return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier); }); if (hydratable) { return true; } - console.log( - 'Measurement is not rehydratable', - TrackingIdentifier, - measurements[i] - ); + console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]); } console.log('No measurements found which were rehydratable'); diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index 8d56f400d39..a1e728bdda7 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -7,11 +7,11 @@ import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSR import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import hydrateStructuredReport from '../utils/hydrateStructuredReport'; +import { useAppConfig } from '@state'; const { formatDate } = utils; -const MEASUREMENT_TRACKING_EXTENSION_ID = - '@ohif/extension-measurement-tracking'; +const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; @@ -20,18 +20,18 @@ function OHIFCornerstoneSRViewport(props) { children, dataSource, displaySets, - viewportIndex, viewportLabel, viewportOptions, servicesManager, extensionManager, } = props; - const { - displaySetService, - cornerstoneViewportService, - measurementService, - } = servicesManager.services; + const [appConfig] = useAppConfig(); + + const { displaySetService, cornerstoneViewportService, measurementService } = + servicesManager.services; + + const viewportId = viewportOptions.viewportId; // SR viewport will always have a single display set if (displaySets.length > 1) { @@ -43,15 +43,10 @@ function OHIFCornerstoneSRViewport(props) { const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementSelected, setMeasurementSelected] = useState(0); const [measurementCount, setMeasurementCount] = useState(1); - const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState( - null - ); - const [ - referencedDisplaySetMetadata, - setReferencedDisplaySetMetadata, - ] = useState(null); + const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState(null); + const [referencedDisplaySetMetadata, setReferencedDisplaySetMetadata] = useState(null); const [element, setElement] = useState(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; // Optional hook into tracking extension, if present. let trackedMeasurements; @@ -76,16 +71,14 @@ function OHIFCornerstoneSRViewport(props) { sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => { measurementService.clearMeasurements(); const { SeriesInstanceUIDs } = hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ); - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUIDs[0] - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]); if (displaySets.length) { viewportGridService.setDisplaySetsForViewports([ { - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], }, ]); @@ -123,11 +116,7 @@ function OHIFCornerstoneSRViewport(props) { const updateViewport = useCallback( newMeasurementSelected => { - const { - StudyInstanceUID, - displaySetInstanceUID, - sopClassUids, - } = srDisplaySet; + const { StudyInstanceUID, displaySetInstanceUID, sopClassUids } = srDisplaySet; if (!StudyInstanceUID || !displaySetInstanceUID) { return; @@ -136,9 +125,7 @@ function OHIFCornerstoneSRViewport(props) { if (sopClassUids && sopClassUids.length > 1) { // Todo: what happens if there are multiple SOP Classes? Why we are // not throwing an error? - console.warn( - 'More than one SOPClassUID in the same series is not yet supported.' - ); + console.warn('More than one SOPClassUID in the same series is not yet supported.'); } _getViewportReferencedDisplaySetData( @@ -160,19 +147,11 @@ function OHIFCornerstoneSRViewport(props) { // imageIdIndex will handle it by updating the viewport, but if they // are the same we just need to use measurementService to jump to the // new measurement - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const csViewport = cornerstoneViewportService.getCornerstoneViewport( - viewportInfo.getViewportId() - ); + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const imageIds = csViewport.getImageIds(); - const imageIdIndex = imageIds.indexOf( - measurements[newMeasurementSelected].imageId - ); + const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId); if (imageIdIndex !== -1) { csViewport.setImageIdIndex(imageIdIndex); @@ -180,7 +159,7 @@ function OHIFCornerstoneSRViewport(props) { } }); }, - [dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] + [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId] ); const getCornerstoneViewport = useCallback(() => { @@ -225,9 +204,10 @@ function OHIFCornerstoneSRViewport(props) { }} onElementEnabled={onElementEnabled} initialImageIndex={initialImageIndex} + isJumpToMeasurementDisabled={true} > ); - }, [activeImageDisplaySetData, viewportIndex, measurementSelected]); + }, [activeImageDisplaySetData, viewportId, measurementSelected]); const onMeasurementChange = useCallback( direction => { @@ -250,12 +230,7 @@ function OHIFCornerstoneSRViewport(props) { setTrackingIdentifiers(newMeasurementSelected); updateViewport(newMeasurementSelected); }, - [ - measurementSelected, - measurementCount, - updateViewport, - setTrackingIdentifiers, - ] + [measurementSelected, measurementCount, updateViewport, setTrackingIdentifiers] ); /** @@ -265,12 +240,10 @@ function OHIFCornerstoneSRViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -337,7 +310,7 @@ function OHIFCornerstoneSRViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -369,7 +342,7 @@ function OHIFCornerstoneSRViewport(props) { getStatusComponent={() => _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isTracked: false, isRehydratable: srDisplaySet.isRehydratable, isLocked, @@ -383,23 +356,19 @@ function OHIFCornerstoneSRViewport(props) { currentSeries: SeriesNumber, seriesDescription: SeriesDescription || '', patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{getCornerstoneViewport()} {childrenWithProps}
@@ -409,7 +378,7 @@ function OHIFCornerstoneSRViewport(props) { OHIFCornerstoneSRViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, viewportLabel: PropTypes.string, @@ -434,9 +403,7 @@ async function _getViewportReferencedDisplaySetData( const { displaySetInstanceUID } = measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); const image0 = referencedDisplaySet.images[0]; const referencedDisplaySetMetadata = { @@ -458,7 +425,7 @@ async function _getViewportReferencedDisplaySetData( function _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isRehydratable, isLocked, sendTrackedMeasurementsEvent, @@ -466,7 +433,7 @@ function _getStatusComponent({ const handleMouseUp = () => { sendTrackedMeasurementsEvent('HYDRATE_SR', { displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, - viewportIndex, + viewportId, }); }; @@ -476,8 +443,7 @@ function _getStatusComponent({ // 1 - Incompatible // 2 - Locked // 3 - Rehydratable / Open - const state = - isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; + const state = isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; let ToolTipMessage = null; let StatusIcon = null; @@ -507,22 +473,25 @@ function _getStatusComponent({ ); break; case 3: - StatusIcon = () => ; - - ToolTipMessage = () => ( -
{`Click ${loadStr} to restore measurements.`}
+ StatusIcon = () => ( + ); + + ToolTipMessage = () =>
{`Click ${loadStr} to restore measurements.`}
; } const StatusArea = () => ( -
-
+
+
SR
{state === 3 && (
@@ -535,7 +504,10 @@ function _getStatusComponent({ return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone/.webpack/webpack.prod.js b/extensions/cornerstone/.webpack/webpack.prod.js index 74868eb44c2..c23590e69f9 100644 --- a/extensions/cornerstone/.webpack/webpack.prod.js +++ b/extensions/cornerstone/.webpack/webpack.prod.js @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index e69de29bb2d..94d61f8dd53 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -0,0 +1,477 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + + +### Bug Fixes + +* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339)) + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + + +### Features + +* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89)) + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + + +### Bug Fixes + +* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5)) + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index a172ee2245c..70a5f2b50d5 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -36,9 +36,9 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/core": "3.6.0", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.20.3", + "@ohif/core": "3.7.0-beta.108", + "@ohif/ui": "3.7.0-beta.108", "dcmjs": "^0.29.6", "dicom-parser": "^1.8.21", "hammerjs": "^2.0.8", @@ -52,10 +52,10 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.1.0", - "@cornerstonejs/core": "^1.1.0", - "@cornerstonejs/streaming-image-volume-loader": "^1.1.0", - "@cornerstonejs/tools": "^1.1.0", + "@cornerstonejs/adapters": "^1.20.3", + "@cornerstonejs/core": "^1.20.3", + "@cornerstonejs/streaming-image-volume-loader": "^1.20.3", + "@cornerstonejs/tools": "^1.20.3", "@kitware/vtk.js": "27.3.1", "html2canvas": "^1.4.1", "lodash.debounce": "4.0.8", diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index a94cc0fc607..3de29dfd611 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -10,17 +10,8 @@ import { utilities as csUtils, } from '@cornerstonejs/core'; import { MeasurementService } from '@ohif/core'; -import { - CinePlayer, - useCine, - useViewportGrid, - Notification, - useViewportDialog, -} from '@ohif/ui'; -import { - IStackViewport, - IVolumeViewport, -} from '@cornerstonejs/core/dist/esm/types'; +import { Notification, useViewportDialog } from '@ohif/ui'; +import { IStackViewport, IVolumeViewport } from '@cornerstonejs/core/dist/esm/types'; import { setEnabledElement } from '../state'; @@ -28,6 +19,8 @@ import './OHIFCornerstoneViewport.css'; import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes'; import CornerstoneServices from '../types/CornerstoneServices'; +import CinePlayer from '../components/CinePlayer'; +import { Types } from '@ohif/core'; const STACK = 'stack'; @@ -46,24 +39,19 @@ function areEqual(prevProps, nextProps) { return false; } - if ( - prevProps.viewportOptions.orientation !== - nextProps.viewportOptions.orientation - ) { + if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) { + return false; + } + + if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) { return false; } - if ( - prevProps.viewportOptions.toolGroupId !== - nextProps.viewportOptions.toolGroupId - ) { + if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) { return false; } - if ( - prevProps.viewportOptions.viewportType !== - nextProps.viewportOptions.viewportType - ) { + if (nextProps.viewportOptions.needsRerendering) { return false; } @@ -79,8 +67,7 @@ function areEqual(prevProps, nextProps) { const foundDisplaySet = nextDisplaySets.find( nextDisplaySet => - nextDisplaySet.displaySetInstanceUID === - prevDisplaySet.displaySetInstanceUID + nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID ); if (!foundDisplaySet) { @@ -95,9 +82,7 @@ function areEqual(prevProps, nextProps) { // check if their imageIds are the same if (foundDisplaySet.images?.length) { for (let j = 0; j < foundDisplaySet.images.length; j++) { - if ( - foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId - ) { + if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) { return false; } } @@ -111,25 +96,24 @@ function areEqual(prevProps, nextProps) { // Then we don't need to worry about the re-renders if the props change. const OHIFCornerstoneViewport = React.memo(props => { const { - viewportIndex, displaySets, dataSource, viewportOptions, displaySetOptions, servicesManager, + commandsManager, onElementEnabled, onElementDisabled, + isJumpToMeasurementDisabled, // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation // of the imageData in the OHIFCornerstoneViewport. This prop is used // to set the initial state of the viewport's first image to render initialImageIndex, } = props; + const viewportId = viewportOptions.viewportId; const [scrollbarHeight, setScrollbarHeight] = useState('100px'); - const [{ isCineEnabled, cines }, cineService] = useCine(); - const [{ activeViewportIndex }] = useViewportGrid(); const [enabledVPElement, setEnabledVPElement] = useState(null); - const elementRef = useRef(); const { @@ -145,74 +129,6 @@ const OHIFCornerstoneViewport = React.memo(props => { } = servicesManager.services as CornerstoneServices; const [viewportDialogState] = useViewportDialog(); - - const cineHandler = () => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { - return; - } - - const cine = cines[viewportIndex]; - const isPlaying = cine.isPlaying || false; - const frameRate = cine.frameRate || 24; - - const validFrameRate = Math.max(frameRate, 1); - - if (isPlaying) { - cineService.playClip(enabledVPElement, { - framesPerSecond: validFrameRate, - }); - } else { - cineService.stopClip(enabledVPElement); - } - }; - - useEffect(() => { - eventTarget.addEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); - - return () => { - cineService.setCine({ id: viewportIndex, isPlaying: false }); - eventTarget.removeEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); - }; - }, [enabledVPElement]); - - useEffect(() => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { - return; - } - - cineHandler(); - - return () => { - if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) { - cineService.stopClip(enabledVPElement); - } - }; - }, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]); - - const cine = cines[viewportIndex]; - const isPlaying = (cine && cine.isPlaying) || false; - - const handleCineClose = () => { - toolbarService.recordInteraction({ - groupId: 'MoreTools', - itemId: 'cine', - interactionType: 'toggle', - commands: [ - { - commandName: 'toggleCine', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - }); - }; - // useCallback for scroll bar height calculation const setImageScrollBarHeight = useCallback(() => { const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`; @@ -227,54 +143,17 @@ const OHIFCornerstoneViewport = React.memo(props => { } }, [elementRef]); - const storePresentation = () => { - const currentPresentation = cornerstoneViewportService.getPresentation( - viewportIndex - ); - if (!currentPresentation || !currentPresentation.presentationIds) return; - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); - const { presentationIds } = currentPresentation; - const { lutPresentationId, positionPresentationId } = presentationIds || {}; - const storeState = {}; - if (lutPresentationId) { - storeState.lutPresentationStore = { - ...lutPresentationStore, - [lutPresentationId]: currentPresentation, - }; - } - if (positionPresentationId) { - storeState.positionPresentationStore = { - ...positionPresentationStore, - [positionPresentationId]: currentPresentation, - }; - } - stateSyncService.store(storeState); - }; - - const cleanUpServices = useCallback(() => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - if (!viewportInfo) { - return; - } - - const viewportId = viewportInfo.getViewportId(); - const renderingEngineId = viewportInfo.getRenderingEngineId(); - const syncGroups = viewportInfo.getSyncGroups(); + const cleanUpServices = useCallback( + viewportInfo => { + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); + toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); - syncGroupService.removeViewportFromSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); - }, [viewportIndex, viewportOptions.viewportId]); + syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups); + }, + [viewportId] + ); const elementEnabledHandler = useCallback( evt => { @@ -284,71 +163,48 @@ const OHIFCornerstoneViewport = React.memo(props => { } const { viewportId, element } = evt.detail; - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - const viewportIndex = viewportInfo.getViewportIndex(); - - setEnabledElement(viewportIndex, element); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + setEnabledElement(viewportId, element); setEnabledVPElement(element); const renderingEngineId = viewportInfo.getRenderingEngineId(); const toolGroupId = viewportInfo.getToolGroupId(); const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.addViewportToToolGroup( - viewportId, - renderingEngineId, - toolGroupId - ); + toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId); - syncGroupService.addViewportToSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); + syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups); if (onElementEnabled) { onElementEnabled(evt); } }, - [viewportIndex, onElementEnabled, toolGroupService] + [viewportId, onElementEnabled, toolGroupService] ); // disable the element upon unmounting useEffect(() => { - cornerstoneViewportService.enableViewport( - viewportIndex, - viewportOptions, - elementRef.current - ); + cornerstoneViewportService.enableViewport(viewportId, elementRef.current); - eventTarget.addEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + eventTarget.addEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); setImageScrollBarHeight(); return () => { - storePresentation(); - - cleanUpServices(); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + if (!viewportInfo) { + return; + } - cornerstoneViewportService.disableElement(viewportIndex); + cleanUpServices(viewportInfo); + cornerstoneViewportService.storePresentation({ viewportId }); if (onElementDisabled) { onElementDisabled(viewportInfo); } - eventTarget.removeEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); }; }, []); @@ -363,10 +219,15 @@ const OHIFCornerstoneViewport = React.memo(props => { useEffect(() => { const { unsubscribe } = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, - async invalidatedDisplaySetInstanceUID => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + async ({ + displaySetInstanceUID: invalidatedDisplaySetInstanceUID, + invalidateData, + }: Types.DisplaySetSeriesMetadataInvalidatedEvent) => { + if (!invalidateData) { + return; + } + + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) { const viewportData = viewportInfo.getViewportData(); @@ -378,18 +239,14 @@ const OHIFCornerstoneViewport = React.memo(props => { ); const keepCamera = true; - cornerstoneViewportService.updateViewport( - viewportIndex, - newViewportData, - keepCamera - ); + cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera); } } ); return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); useEffect(() => { // handle the default viewportType to be stack @@ -408,33 +265,37 @@ const OHIFCornerstoneViewport = React.memo(props => { // The presentation state will have been stored previously by closing // a viewport. Otherwise, this viewport will be unchanged and the // presentation information will be directly carried over. - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); + const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState(); const { presentationIds } = viewportOptions; const presentations = { - positionPresentation: - positionPresentationStore[presentationIds?.positionPresentationId], - lutPresentation: - lutPresentationStore[presentationIds?.lutPresentationId], + positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId], + lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId], }; let measurement; - if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) { + if (cacheJumpToMeasurementEvent?.viewportId === viewportId) { measurement = cacheJumpToMeasurementEvent.measurement; // Delete the position presentation so that viewport navigates direct presentations.positionPresentation = null; cacheJumpToMeasurementEvent = null; } + // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component + // Used for segmentation hydration right now, since the logic to decide whether + // a viewport needs to render a segmentation lives inside the CornerstoneViewportService + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off. + if (viewportOptions.needsRerendering) { + viewportOptions.needsRerendering = false; + } + cornerstoneViewportService.setViewportData( - viewportIndex, + viewportId, viewportData, viewportOptions, displaySetOptions, presentations ); - if (measurement) { cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid); } @@ -454,11 +315,15 @@ const OHIFCornerstoneViewport = React.memo(props => { * the cache for jumping to see if there is any jump queued, then we jump to the correct slice. */ useEffect(() => { + if (isJumpToMeasurementDisabled) { + return; + } + const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -468,7 +333,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -477,17 +342,14 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribeFromJumpToMeasurementEvents(); }; - }, [displaySets, elementRef, viewportIndex]); + }, [displaySets, elementRef, viewportId]); return (
@@ -499,34 +361,20 @@ const OHIFCornerstoneViewport = React.memo(props => { ref={elementRef} >
- {isCineEnabled && ( - - cineService.setCine({ - id: activeViewportIndex, - isPlaying, - }) - } - onFrameRateChange={frameRate => - cineService.setCine({ - id: activeViewportIndex, - frameRate, - }) - } - /> - )} +
- {viewportDialogState.viewportIndex === viewportIndex && ( + {viewportDialogState.viewportId === viewportId && ( displaySet.displaySetInstanceUID - ); const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => { cacheJumpToMeasurementEvent = props; - const { viewportIndex: jumpIndex, measurement, isConsumed } = props; - if (!measurement || isConsumed) return; + const { viewportId: jumpId, measurement, isConsumed } = props; + if (!measurement || isConsumed) { + return; + } if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) { // Decide on which viewport should handle this - cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump( - jumpIndex, - measurement.displaySetInstanceUID, - { referencedImageId: measurement.referencedImageId } - ); + cacheJumpToMeasurementEvent.cornerstoneViewport = + cornerstoneViewportService.getViewportIdToJump( + jumpId, + measurement.displaySetInstanceUID, + { referencedImageId: measurement.referencedImageId } + ); } - if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) { + if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) { return; } _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -590,20 +438,22 @@ function _checkForCachedJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService ) { - if (!cacheJumpToMeasurementEvent) return; + if (!cacheJumpToMeasurementEvent) { + return; + } if (cacheJumpToMeasurementEvent.isConsumed) { cacheJumpToMeasurementEvent = null; return; } - const displaysUIDs = displaySets.map( - displaySet => displaySet.displaySetInstanceUID - ); - if (!displaysUIDs?.length) return; + const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID); + if (!displaysUIDs?.length) { + return; + } // Jump to measurement if the measurement exists const { measurement } = cacheJumpToMeasurementEvent; @@ -612,7 +462,7 @@ function _checkForCachedJumpToMeasurementEvents( _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -625,7 +475,7 @@ function _checkForCachedJumpToMeasurementEvents( function _jumpToMeasurement( measurement, targetElementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -639,27 +489,19 @@ function _jumpToMeasurement( return; } - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager // to set it properly // setCornerstoneMeasurementActive(measurement); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); const enabledElement = getEnabledElement(targetElement); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - if (enabledElement) { // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. - const viewport = enabledElement.viewport as - | IStackViewport - | IVolumeViewport; + const viewport = enabledElement.viewport as IStackViewport | IVolumeViewport; let imageIdIndex = 0; let viewportCameraDirectionMatch = true; @@ -667,14 +509,9 @@ function _jumpToMeasurement( if (viewport instanceof StackViewport) { const imageIds = viewport.getImageIds(); imageIdIndex = imageIds.findIndex(imageId => { - const { - SOPInstanceUID: aSOPInstanceUID, - frameNumber: aFrameNumber, - } = getSOPInstanceAttributes(imageId); - return ( - aSOPInstanceUID === SOPInstanceUID && - (!frameNumber || frameNumber === aFrameNumber) - ); + const { SOPInstanceUID: aSOPInstanceUID, frameNumber: aFrameNumber } = + getSOPInstanceAttributes(imageId); + return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber); }); } else { // for volume viewport we can't rely on the imageIdIndex since it can be @@ -689,10 +526,7 @@ function _jumpToMeasurement( // should compare abs for both planes since the direction can be flipped if ( measurementViewPlane && - !csUtils.isEqual( - measurementViewPlane.map(Math.abs), - viewportViewPlane.map(Math.abs) - ) + !csUtils.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs)) ) { viewportCameraDirectionMatch = false; } @@ -716,21 +550,22 @@ function _jumpToMeasurement( // Component displayName OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport'; +OHIFCornerstoneViewport.defaultProps = { + isJumpToMeasurementDisabled: false, +}; + OHIFCornerstoneViewport.propTypes = { - viewportIndex: PropTypes.number.isRequired, displaySets: PropTypes.array.isRequired, dataSource: PropTypes.object.isRequired, viewportOptions: PropTypes.object, displaySetOptions: PropTypes.arrayOf(PropTypes.any), servicesManager: PropTypes.object.isRequired, onElementEnabled: PropTypes.func, + isJumpToMeasurementDisabled: PropTypes.bool, // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation // of the imageData in the OHIFCornerstoneViewport. This prop is used // to set the initial state of the viewport's first image to render - initialImageIdOrIndex: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default OHIFCornerstoneViewport; diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx index ea0c42cb90a..69f7b10faa2 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx @@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers'; import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator'; function CornerstoneOverlays(props) { - const { viewportIndex, element, scrollbarHeight, servicesManager } = props; + const { viewportId, element, scrollbarHeight, servicesManager } = props; const { cornerstoneViewportService } = servicesManager.services; const [imageSliceData, setImageSliceData] = useState({ imageIndex: 0, @@ -18,7 +18,7 @@ function CornerstoneOverlays(props) { const { unsubscribe } = cornerstoneViewportService.subscribe( cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => { - if (props.viewportIndex !== viewportIndex) { + if (props.viewportId !== viewportId) { return; } @@ -29,15 +29,14 @@ function CornerstoneOverlays(props) { return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); if (!element) { return null; } if (viewportData) { - const viewportInfo = - cornerstoneViewportService.getViewportInfoByIndex(viewportIndex); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) { return null; @@ -47,7 +46,7 @@ function CornerstoneOverlays(props) { return (
@@ -74,7 +73,7 @@ function CornerstoneOverlays(props) { element={element} viewportData={viewportData} servicesManager={servicesManager} - viewportIndex={viewportIndex} + viewportId={viewportId} />
); diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx index bf7665200c9..8745fdec7f0 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx @@ -3,12 +3,7 @@ import { vec3 } from 'gl-matrix'; import PropTypes from 'prop-types'; import { metaData, Enums, utilities } from '@cornerstonejs/core'; import { ViewportOverlay } from '@ohif/ui'; -import { - formatPN, - formatDICOMDate, - formatDICOMTime, - formatNumberPrecision, -} from './utils'; +import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils'; import { InstanceMetadata } from 'platform/core/src/types'; import { ServicesManager } from '@ohif/core'; import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types'; @@ -21,7 +16,6 @@ interface OverlayItemProps { element: any; viewportData: any; imageSliceData: ImageSliceData; - viewportIndex: number | null; servicesManager: ServicesManager; instance: InstanceMetadata; customization: any; @@ -56,13 +50,9 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) { style={{ color: (customization && customization.color) || undefined }} > W: - - {windowWidth.toFixed(0)} - + {windowWidth.toFixed(0)} L: - - {windowCenter.toFixed(0)} - + {windowCenter.toFixed(0)}
); } @@ -114,14 +104,11 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, }) { - const { - toolbarService, - cornerstoneViewportService, - customizationService, - } = servicesManager.services; + const { toolbarService, cornerstoneViewportService, customizationService } = + servicesManager.services; const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null }); const [scale, setScale] = useState(1); const [activeTools, setActiveTools] = useState([]); @@ -150,15 +137,10 @@ function CustomizableViewportOverlay({ const instanceNumber = useMemo(() => { if (viewportData != null) { - return _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService - ); + return _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService); } return null; - }, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]); + }, [viewportData, viewportId, imageIndex, cornerstoneViewportService]); /** * Initial toolbar state @@ -179,10 +161,7 @@ function CustomizableViewportOverlay({ } const { lower, upper } = range; - const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel( - lower, - upper - ); + const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper); setVOI({ windowCenter, windowWidth }); }; @@ -192,7 +171,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -205,9 +184,7 @@ function CustomizableViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -226,8 +203,7 @@ function CustomizableViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -237,7 +213,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData, cornerstoneViewportService, element]); + }, [viewportId, viewportData, cornerstoneViewportService, element]); /** * Updating the active tools when the toolbar changes @@ -262,7 +238,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customization: item, formatters: { @@ -296,7 +272,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customizationService, instance, @@ -343,9 +319,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -356,9 +330,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -379,7 +351,7 @@ function _getViewportInstance(viewportData, imageIndex) { if (viewportData.viewportType === Enums.ViewportType.STACK) { imageId = viewportData.data.imageIds[imageIndex]; } else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) { - const volumes = viewportData.volumes; + const volumes = viewportData.data; if (volumes && volumes.length == 1) { const volume = volumes[0]; imageId = volume.imageIds[imageIndex]; @@ -388,12 +360,7 @@ function _getViewportInstance(viewportData, imageIndex) { return imageId ? metaData.get('instance', imageId) || {} : {}; } -function _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService -) { +function _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) { let instanceNumber; if (viewportData.viewportType === Enums.ViewportType.STACK) { @@ -406,7 +373,7 @@ function _getInstanceNumber( instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -436,12 +403,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) { // Since volume viewports can be in any view direction, they can render // a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber -function _getInstanceNumberFromVolume( - viewportData, - imageIndex, - viewportIndex, - cornerstoneViewportService -) { +function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService) { const volumes = viewportData.volumes; // Todo: support fusion of acquisition plane which has instanceNumber @@ -452,9 +414,7 @@ function _getInstanceNumberFromVolume( const volume = volumes[0]; const { direction, imageIds } = volume; - const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!cornerstoneViewport) { return; @@ -477,8 +437,7 @@ function _getInstanceNumberFromVolume( return {}; } - const { instanceNumber } = - metaData.get('generalImageModule', imageId) || {}; + const { instanceNumber } = metaData.get('generalImageModule', imageId) || {}; return parseInt(instanceNumber); } } @@ -486,7 +445,7 @@ function _getInstanceNumberFromVolume( CustomizableViewportOverlay.propTypes = { viewportData: PropTypes.object, imageIndex: PropTypes.number, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, }; export default CustomizableViewportOverlay; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx index 074e0a969ff..2f7b71a45da 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx @@ -7,34 +7,24 @@ import { ServicesManger } from '@ohif/core'; function CornerstoneImageScrollbar({ viewportData, - viewportIndex, + viewportId, element, imageSliceData, setImageSliceData, scrollbarHeight, servicesManager, }) { - const { - cineService, - cornerstoneViewportService, - } = (servicesManager as ServicesManger).services; + const { cineService, cornerstoneViewportService } = (servicesManager as ServicesManger).services; - const onImageScrollbarChange = (imageIndex, viewportIndex) => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const viewportId = viewportInfo.getViewportId(); - const viewport = cornerstoneViewportService.getCornerstoneViewport( - viewportId - ); + const onImageScrollbarChange = (imageIndex, viewportId) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const { isCineEnabled } = cineService.getState(); if (isCineEnabled) { // on image scrollbar change, stop the CINE if it is playing cineService.stopClip(element); - cineService.setCine({ id: viewportIndex, isPlaying: false }); + cineService.setCine({ id: viewportId, isPlaying: false }); } csToolsUtils.jumpToSlice(viewport.element, { @@ -48,9 +38,7 @@ function CornerstoneImageScrollbar({ return; } - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -79,7 +67,7 @@ function CornerstoneImageScrollbar({ const { imageIndex, numberOfSlices } = sliceData; setImageSliceData({ imageIndex, numberOfSlices }); } - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); useEffect(() => { if (viewportData?.viewportType !== Enums.ViewportType.STACK) { @@ -95,16 +83,10 @@ function CornerstoneImageScrollbar({ }); }; - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); }; }, [viewportData, element]); @@ -122,19 +104,14 @@ function CornerstoneImageScrollbar({ element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); return () => { - element.removeEventListener( - Enums.Events.VOLUME_NEW_IMAGE, - updateVolumeIndex - ); + element.removeEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); }; }, [viewportData, element]); return ( onImageScrollbarChange(evt, viewportIndex)} - max={ - imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 - } + onChange={evt => onImageScrollbarChange(evt, viewportId)} + max={imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0} height={scrollbarHeight} value={imageSliceData.imageIndex} /> @@ -143,7 +120,7 @@ function CornerstoneImageScrollbar({ CornerstoneImageScrollbar.propTypes = { viewportData: PropTypes.object, - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, element: PropTypes.instanceOf(Element), scrollbarHeight: PropTypes.string, imageSliceData: PropTypes.object.isRequired, diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx index fc483b5f475..70e03e534cb 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx @@ -33,26 +33,14 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { }; useEffect(() => { - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); - element.addEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.addEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); - element.removeEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); }; @@ -61,8 +49,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { if (error) { return ( <> -
-
+
+

Error Loading Image

An error has occurred.

@@ -78,8 +66,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { return ( // IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from // interacting with the mouse, since scrolling should propagate to the viewport underneath -
-
+
+

Loading...

diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx index 8e431ac525e..2f33456a42c 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx @@ -13,16 +13,13 @@ import { vec3 } from 'gl-matrix'; import './ViewportOrientationMarkers.css'; -const { - getOrientationStringLPS, - invertOrientationStringLPS, -} = utilities.orientation; +const { getOrientationStringLPS, invertOrientationStringLPS } = utilities.orientation; function ViewportOrientationMarkers({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, orientationMarkers = ['top', 'left'], }) { @@ -33,9 +30,7 @@ function ViewportOrientationMarkers({ const { cornerstoneViewportService } = servicesManager.services; useEffect(() => { - const cameraModifiedListener = ( - evt: Types.EventTypes.CameraModifiedEvent - ) => { + const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => { const { rotation, previousCamera, camera } = evt.detail; if (rotation !== undefined) { @@ -57,16 +52,10 @@ function ViewportOrientationMarkers({ } }; - element.addEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.addEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); return () => { - element.removeEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); }; }, []); @@ -85,8 +74,7 @@ function ViewportOrientationMarkers({ return false; } - ({ rowCosines, columnCosines } = - metaData.get('imagePlaneModule', imageId) || {}); + ({ rowCosines, columnCosines } = metaData.get('imagePlaneModule', imageId) || {}); } else { if (!element || !getEnabledElement(element)) { return ''; @@ -114,9 +102,7 @@ function ViewportOrientationMarkers({ flipHorizontal ); - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { console.log('ViewportOrientationMarkers::No viewport'); @@ -126,9 +112,7 @@ function ViewportOrientationMarkers({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? csUtils.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? csUtils.isEqual(backgroundColor, [1, 1, 1]) : false; return orientationMarkers.map((m, index) => (
{ element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -86,9 +80,7 @@ function CornerstoneViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -107,8 +99,7 @@ function CornerstoneViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -118,7 +109,7 @@ function CornerstoneViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); const getTopLeftContent = useCallback(() => { const { windowWidth, windowCenter } = voi; @@ -168,7 +159,7 @@ function CornerstoneViewportOverlay({ instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -183,15 +174,13 @@ function CornerstoneViewportOverlay({
); - }, [imageSliceData, viewportData, viewportIndex]); + }, [imageSliceData, viewportData, viewportId]); if (!viewportData) { return null; } - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { return null; @@ -201,9 +190,7 @@ function CornerstoneViewportOverlay({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? utilities.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false; return ( { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (!viewportInfo) { console.warn('No viewport found for viewportId:', viewportId); return; } - const viewportIndex = viewportInfo.getViewportIndex(); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }, arrowTextCallback: ({ callback, data }) => { callInputDialog(uiDialogService, data, callback); }, + cleanUpCrosshairs: () => { + // if the crosshairs tool is active, deactivate it and set window level active + // since we are going back to main non-mpr HP + const activeViewportToolGroup = toolGroupService.getToolGroup(null); + + if (activeViewportToolGroup._toolInstances?.Crosshairs?.mode === Enums.ToolModes.Active) { + actions.toolbarServiceRecordInteraction({ + interactionType: 'tool', + commands: [ + { + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + } + }, toggleCine: () => { const { viewports } = viewportGridService.getState(); const { isCineEnabled } = cineService.getState(); cineService.setIsCineEnabled(!isCineEnabled); toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } }); - viewports.forEach((_, index) => - cineService.setCine({ id: index, isPlaying: false }) - ); + viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false })); }, setWindowLevel({ window, level, toolGroupId }) { // convert to numbers @@ -259,9 +256,7 @@ function commandsModule({ const windowCenterNum = Number(level); const { viewportId } = _getActiveViewportEnabledElement(); - const viewportToolGroupId = toolGroupService.getToolGroupForViewport( - viewportId - ); + const viewportToolGroupId = toolGroupService.getToolGroupForViewport(viewportId); if (toolGroupId && toolGroupId !== viewportToolGroupId) { return; @@ -271,10 +266,7 @@ function commandsModule({ const renderingEngine = cornerstoneViewportService.getRenderingEngine(); const viewport = renderingEngine.getViewport(viewportId); - const { lower, upper } = csUtils.windowLevel.toLowHighRange( - windowWidthNum, - windowCenterNum - ); + const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum); viewport.setProperties({ voiRange: { @@ -291,8 +283,12 @@ function commandsModule({ toolbarServiceRecordInteraction: props => { toolbarService.recordInteraction(props); }, - - setToolActive: ({ toolName, toolGroupId = null }) => { + // Enable or disable a toggleable command, without calling the activation + // Used to setup already active tools from hanging protocols + setToolbarToggled: props => { + toolbarService.setToggled(props.toolId, props.isActive ?? true); + }, + setToolActive: ({ toolName, toolGroupId = null, toggledState }) => { if (toolName === 'Crosshairs') { const activeViewportToolGroup = toolGroupService.getToolGroup(null); @@ -309,29 +305,15 @@ function commandsModule({ } } - const { viewports } = viewportGridService.getState() || { - viewports: [], - }; - - const toolGroup = toolGroupService.getToolGroup(toolGroupId); - const toolGroupViewportIds = toolGroup?.getViewportIds?.(); + const { viewports } = viewportGridService.getState(); - // if toolGroup has been destroyed, or its viewports have been removed - if (!toolGroupViewportIds || !toolGroupViewportIds.length) { + if (!viewports.size) { return; } - const filteredViewports = viewports.filter(viewport => { - if (!viewport.viewportOptions) { - return false; - } - - return toolGroupViewportIds.includes( - viewport.viewportOptions.viewportId - ); - }); + const toolGroup = toolGroupService.getToolGroup(toolGroupId); - if (!filteredViewports.length) { + if (!toolGroup) { return; } @@ -357,6 +339,14 @@ function commandsModule({ toolGroup.setToolPassive(activeToolName); } } + + // If there is a toggle state, then simply set the enabled/disabled state without + // setting the tool active. + if (toggledState != null) { + toggledState ? toolGroup.setToolEnabled(toolName) : toolGroup.setToolDisabled(toolName); + return; + } + // Set the new toolName to be active toolGroup.setToolActive(toolName, { bindings: [ @@ -367,13 +357,9 @@ function commandsModule({ }); }, showDownloadViewportModal: () => { - const { activeViewportIndex } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); - if ( - !cornerstoneViewportService.getCornerstoneViewportByIndex( - activeViewportIndex - ) - ) { + if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) { // Cannot download a non-cornerstone viewport (image). uiNotificationService.show({ title: 'Download Image', @@ -390,7 +376,7 @@ function commandsModule({ content: CornerstoneViewportDownloadForm, title: 'Download High Quality Image', contentProps: { - activeViewportIndex, + activeViewportId, onClose: uiModalService.hide, cornerstoneViewportService, }, @@ -457,11 +443,9 @@ function commandsModule({ const { viewport } = enabledElement; - if (viewport instanceof StackViewport) { - const { invert } = viewport.getProperties(); - viewport.setProperties({ invert: !invert }); - viewport.render(); - } + const { invert } = viewport.getProperties(); + viewport.setProperties({ invert: !invert }); + viewport.render(); }, resetViewport: () => { const enabledElement = _getActiveViewportEnabledElement(); @@ -476,7 +460,7 @@ function commandsModule({ viewport.resetProperties(); viewport.resetCamera(); } else { - // Todo: add reset properties for volume viewport + viewport.resetProperties(); viewport.resetCamera(); } @@ -514,9 +498,7 @@ function commandsModule({ } viewport = enabledElement.viewport; } else { - viewport = cornerstoneViewportService.getCornerstoneViewport( - gridViewport.id - ); + viewport = cornerstoneViewportService.getCornerstoneViewport(gridViewport.id); } // Get number of slices @@ -526,14 +508,12 @@ function commandsModule({ if (viewport instanceof StackViewport) { numberOfSlices = viewport.getImageIds().length; } else if (viewport instanceof VolumeViewport) { - numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport) - .numberOfSlices; + numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices; } else { throw new Error('Unsupported viewport type'); } - const jumpIndex = - imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; + const jumpIndex = imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; if (jumpIndex >= numberOfSlices || jumpIndex < 0) { throw new Error(`Can't jump to ${imageIndex}`); } @@ -554,15 +534,8 @@ function commandsModule({ cstUtils.scroll(viewport, options); }, - setViewportColormap: ({ - viewportIndex, - displaySetInstanceUID, - colormap, - immediate = false, - }) => { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, immediate = false }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const actorEntries = viewport.getActors(); @@ -578,37 +551,29 @@ function commandsModule({ viewport.render(); } }, - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + changeActiveViewport: ({ direction = 1 }) => { + const { activeViewportId, viewports } = viewportGridService.getState(); + const viewportIds = Array.from(viewports.keys()); + const currentIndex = viewportIds.indexOf(activeViewportId); const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); + (currentIndex + direction + viewportIds.length) % viewportIds.length; + viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string); }, + toggleStackImageSync: ({ toggledState }) => { toggleStackImageSync({ - getEnabledElement, servicesManager, toggledState, }); }, - toggleReferenceLines: ({ toggledState }) => { - const { activeViewportIndex } = viewportGridService.getState(); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - activeViewportIndex - ); + setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => { + if (!viewportId) { + const { activeViewportId } = viewportGridService.getState(); + viewportId = activeViewportId; + } - const viewportId = viewportInfo.getViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); - if (!toggledState) { - toolGroup.setToolDisabled(ReferenceLinesTool.toolName); - } - toolGroup.setToolConfiguration( ReferenceLinesTool.toolName, { @@ -616,7 +581,9 @@ function commandsModule({ }, true // overwrite ); - toolGroup.setToolEnabled(ReferenceLinesTool.toolName); + }, + storePresentation: ({ viewportId }) => { + cornerstoneViewportService.storePresentation({ viewportId }); }, }; @@ -673,10 +640,11 @@ function commandsModule({ options: { rotation: -90 }, }, incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, + commandFn: actions.changeActiveViewport, }, decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, + commandFn: actions.changeActiveViewport, + options: { direction: -1 }, }, flipViewportHorizontal: { commandFn: actions.flipViewportHorizontal, @@ -739,8 +707,17 @@ function commandsModule({ toggleStackImageSync: { commandFn: actions.toggleStackImageSync, }, - toggleReferenceLines: { - commandFn: actions.toggleReferenceLines, + setSourceViewportForReferenceLinesTool: { + commandFn: actions.setSourceViewportForReferenceLinesTool, + }, + storePresentation: { + commandFn: actions.storePresentation, + }, + setToolbarToggled: { + commandFn: actions.setToolbarToggled, + }, + cleanUpCrosshairs: { + commandFn: actions.cleanUpCrosshairs, }, }; diff --git a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx new file mode 100644 index 00000000000..92bb1510327 --- /dev/null +++ b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx @@ -0,0 +1,97 @@ +import React, { useEffect } from 'react'; +import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui'; +import { Enums, eventTarget } from '@cornerstonejs/core'; + +function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) { + const { toolbarService, customizationService } = servicesManager.services; + const [{ isCineEnabled, cines }, cineService] = useCine(); + const [{ activeViewportId }] = useViewportGrid(); + + const { component: CinePlayerComponent = CinePlayer } = + customizationService.get('cinePlayer') ?? {}; + + const handleCineClose = () => { + toolbarService.recordInteraction({ + groupId: 'MoreTools', + interactionType: 'toggle', + commands: [ + { + commandName: 'toggleCine', + commandOptions: {}, + toolName: 'cine', + context: 'CORNERSTONE', + }, + ], + }); + }; + + const cineHandler = () => { + if (!cines || !cines[viewportId] || !enabledVPElement) { + return; + } + + const cine = cines[viewportId]; + const isPlaying = cine.isPlaying || false; + const frameRate = cine.frameRate || 24; + + const validFrameRate = Math.max(frameRate, 1); + + if (isPlaying) { + cineService.playClip(enabledVPElement, { + framesPerSecond: validFrameRate, + }); + } else { + cineService.stopClip(enabledVPElement); + } + }; + + useEffect(() => { + eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler); + + return () => { + cineService.setCine({ id: viewportId, isPlaying: false }); + eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler); + }; + }, [enabledVPElement]); + + useEffect(() => { + if (!cines || !cines[viewportId] || !enabledVPElement) { + return; + } + + cineHandler(); + + return () => { + if (enabledVPElement && cines?.[viewportId]?.isPlaying) { + cineService.stopClip(enabledVPElement); + } + }; + }, [cines, viewportId, cineService, enabledVPElement, cineHandler]); + + const cine = cines[viewportId]; + const isPlaying = (cine && cine.isPlaying) || false; + + return ( + isCineEnabled && ( + + cineService.setCine({ + id: activeViewportId, + isPlaying, + }) + } + onFrameRateChange={frameRate => + cineService.setCine({ + id: activeViewportId, + frameRate, + }) + } + /> + ) + ); +} + +export default WrappedCinePlayer; diff --git a/extensions/cornerstone/src/components/CinePlayer/index.ts b/extensions/cornerstone/src/components/CinePlayer/index.ts new file mode 100644 index 00000000000..d31d4bed763 --- /dev/null +++ b/extensions/cornerstone/src/components/CinePlayer/index.ts @@ -0,0 +1,3 @@ +import CinePlayer from './CinePlayer'; + +export default CinePlayer; diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css index 55ddf7922e9..d37d2a6a649 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css @@ -1,6 +1,23 @@ .dicom-upload-drop-area-border-dash { - background-image: repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%); - background-position: left top, left bottom, left top, right top; + background-image: repeating-linear-gradient( + to right, + #7bb2ce 0%, + #7bb2ce 50%, + transparent 50%, + transparent 100% + ), + repeating-linear-gradient(to right, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%); + background-position: + left top, + left bottom, + left top, + right top; background-repeat: repeat-x, repeat-x, repeat-y, repeat-y; - background-size: 20px 3px, 20px 3px, 3px 20px, 3px 20px; + background-size: + 20px 3px, + 20px 3px, + 3px 20px, + 3px 20px; } diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx index 26e596e9522..8284ee07055 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import DicomFileUploader from '../../utils/DicomFileUploader'; import DicomUploadProgress from './DicomUploadProgress'; -import { Button } from '@ohif/ui'; +import { Button, ButtonEnums } from '@ohif/ui'; import './DicomUpload.css'; type DicomUploadProps = { @@ -14,19 +14,13 @@ type DicomUploadProps = { onStarted: () => void; }; -function DicomUpload({ - dataSource, - onComplete, - onStarted, -}: DicomUploadProps): ReactElement { +function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): ReactElement { const baseClassNames = 'min-h-[480px] flex flex-col bg-black select-none'; const [dicomFileUploaderArr, setDicomFileUploaderArr] = useState([]); const onDrop = useCallback(async acceptedFiles => { onStarted(); - setDicomFileUploaderArr( - acceptedFiles.map(file => new DicomFileUploader(file, dataSource)) - ); + setDicomFileUploaderArr(acceptedFiles.map(file => new DicomFileUploader(file, dataSource))); }, []); const getDropZoneComponent = (): ReactElement => { @@ -40,15 +34,16 @@ function DicomUpload({ {({ getRootProps }) => (
- + {({ getRootProps, getInputProps }) => (
)}
- + {({ getRootProps, getInputProps }) => (
or drag images or folders here
-
- (DICOM files supported) -
+
(DICOM files supported)
)} @@ -99,9 +93,7 @@ function DicomUpload({ />
) : ( -
- {getDropZoneComponent()} -
+
{getDropZoneComponent()}
)} ); diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx index d16ee4b9f05..c09d9698810 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useEffect, - useRef, - useState, - ReactElement, -} from 'react'; +import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react'; import PropTypes from 'prop-types'; import { Button, Icon, ProgressLoadingBar } from '@ohif/ui'; import DicomFileUploader, { @@ -39,18 +33,14 @@ const BASE_INTERVAL_TIME = 15000; // calculate the upload rate. const UPLOAD_RATE_THRESHOLD = 75; -const NO_WRAP_ELLIPSIS_CLASS_NAMES = - 'text-ellipsis whitespace-nowrap overflow-hidden'; +const NO_WRAP_ELLIPSIS_CLASS_NAMES = 'text-ellipsis whitespace-nowrap overflow-hidden'; function DicomUploadProgress({ dicomFileUploaderArr, onComplete, }: DicomUploadProgressProps): ReactElement { const [totalUploadSize] = useState( - dicomFileUploaderArr.reduce( - (acc, fileUploader) => acc + fileUploader.getFileSize(), - 0 - ) + dicomFileUploaderArr.reduce((acc, fileUploader) => acc + fileUploader.getFileSize(), 0) ); const currentUploadSizeRef = useRef(0); @@ -83,15 +73,13 @@ function DicomUploadProgress({ let intervalStartTime = Date.now(); const setUploadRateRef = () => { - const uploadSizeFromStartOfInterval = - currentUploadSizeRef.current - intervalStartUploadSize; + const uploadSizeFromStartOfInterval = currentUploadSizeRef.current - intervalStartUploadSize; const now = Date.now(); const timeSinceStartOfInterval = now - intervalStartTime; // Calculate and set the upload rate (ref) - uploadRateRef.current = - uploadSizeFromStartOfInterval / timeSinceStartOfInterval; + uploadRateRef.current = uploadSizeFromStartOfInterval / timeSinceStartOfInterval; // Reset the interval starting values. intervalStartUploadSize = currentUploadSizeRef.current; @@ -134,28 +122,19 @@ function DicomUploadProgress({ const updateProgress = (percentComplete: number) => { const previousFileUploadSize = currentFileUploadSize; - currentFileUploadSize = Math.round( - (percentComplete / 100) * fileUploader.getFileSize() - ); + currentFileUploadSize = Math.round((percentComplete / 100) * fileUploader.getFileSize()); currentUploadSizeRef.current = Math.min( totalUploadSize, - currentUploadSizeRef.current - - previousFileUploadSize + - currentFileUploadSize + currentUploadSizeRef.current - previousFileUploadSize + currentFileUploadSize ); - setPercentComplete( - (currentUploadSizeRef.current / totalUploadSize) * 100 - ); + setPercentComplete((currentUploadSizeRef.current / totalUploadSize) * 100); if (uploadRateRef.current !== 0) { - const uploadSizeRemaining = - totalUploadSize - currentUploadSizeRef.current; + const uploadSizeRemaining = totalUploadSize - currentUploadSizeRef.current; - const timeRemaining = Math.round( - uploadSizeRemaining / uploadRateRef.current - ); + const timeRemaining = Math.round(uploadSizeRemaining / uploadRateRef.current); if (currentTimeRemaining === null) { currentTimeRemaining = timeRemaining; @@ -168,9 +147,7 @@ function DicomUploadProgress({ // due to rounding, inaccuracies in the estimate and slight variations // in upload rates over time. if (timeRemaining < ONE_MINUTE) { - const currentSecondsRemaining = Math.ceil( - currentTimeRemaining / ONE_SECOND - ); + const currentSecondsRemaining = Math.ceil(currentTimeRemaining / ONE_SECOND); const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); const delta = secondsRemaining - currentSecondsRemaining; if (delta < 0 || delta > 2) { @@ -181,9 +158,7 @@ function DicomUploadProgress({ } if (timeRemaining < ONE_HOUR) { - const currentMinutesRemaining = Math.ceil( - currentTimeRemaining / ONE_MINUTE - ); + const currentMinutesRemaining = Math.ceil(currentTimeRemaining / ONE_MINUTE); const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); const delta = minutesRemaining - currentMinutesRemaining; if (delta < 0 || delta > 2) { @@ -199,9 +174,7 @@ function DicomUploadProgress({ } }; - const progressCallback = ( - progressEvent: DicomFileUploaderProgressEvent - ) => { + const progressCallback = (progressEvent: DicomFileUploaderProgressEvent) => { updateProgress(progressEvent.percentComplete); }; @@ -249,16 +222,12 @@ function DicomUploadProgress({ if (timeRemaining < ONE_MINUTE) { const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); - return `${secondsRemaining} ${ - secondsRemaining === 1 ? 'second' : 'seconds' - }`; + return `${secondsRemaining} ${secondsRemaining === 1 ? 'second' : 'seconds'}`; } if (timeRemaining < ONE_HOUR) { const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); - return `${minutesRemaining} ${ - minutesRemaining === 1 ? 'minute' : 'minutes' - }`; + return `${minutesRemaining} ${minutesRemaining === 1 ? 'minute' : 'minutes'}`; } const hoursRemaining = Math.ceil(timeRemaining / ONE_HOUR); @@ -278,9 +247,7 @@ function DicomUploadProgress({ const showInfiniteProgressBar = useCallback((): boolean => { return ( getPercentCompleteRounded() < 1 && - (progressBarContainerRef?.current?.offsetWidth ?? 0) * - (percentComplete / 100) < - 1 + (progressBarContainerRef?.current?.offsetWidth ?? 0) * (percentComplete / 100) < 1 ); }, [getPercentCompleteRounded, percentComplete]); @@ -300,17 +267,13 @@ function DicomUploadProgress({ const getNumCompletedAndTimeRemainingComponent = (): ReactElement => { return ( -
+
{numFilesCompleted === dicomFileUploaderArr.length ? ( <> - {`${ - dicomFileUploaderArr.length - } ${ + {`${dicomFileUploaderArr.length} ${ dicomFileUploaderArr.length > 1 ? 'files' : 'file' } completed.`} +
+ ); + })} +
+ + )} +
+
+ ); +} + +export default ItemListComponent; diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx new file mode 100644 index 00000000000..23e9841b068 --- /dev/null +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import { SidePanel } from '@ohif/ui'; +import { PanelService, ServicesManager } from '@ohif/core'; + +export type SidePanelWithServicesProps = { + servicesManager: ServicesManager; + side: 'left' | 'right'; + className: string; + activeTabIndex: number; + tabs: any; +}; + +const SidePanelWithServices = ({ + servicesManager, + side, + className, + activeTabIndex: activeTabIndexProp, + tabs, +}) => { + const panelService: PanelService = servicesManager?.services?.panelService; + + // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. + // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. + const [hasBeenOpened, setHasBeenOpened] = useState(false); + const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp); + + useEffect(() => { + if (panelService) { + const activatePanelSubscription = panelService.subscribe( + panelService.EVENTS.ACTIVATE_PANEL, + (activatePanelEvent: Types.ActivatePanelEvent) => { + if (!hasBeenOpened || activatePanelEvent.forceActive) { + const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); + if (tabIndex !== -1) { + setActiveTabIndex(tabIndex); + } + } + } + ); + + return () => { + activatePanelSubscription.unsubscribe(); + }; + } + }, [tabs, hasBeenOpened, panelService]); + + return ( + { + setHasBeenOpened(true); + }} + > + ); +}; + +export default SidePanelWithServices; diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx index 82879bf02af..9d65b971d87 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx @@ -19,10 +19,7 @@ export default class ContextMenuController { services: Types.Services; menuItems: Menu[] | MenuItem[]; - constructor( - servicesManager: ServicesManager, - commandsManager: CommandsManager - ) { + constructor(servicesManager: ServicesManager, commandsManager: CommandsManager) { this.services = servicesManager.services as Obj; this.commandsManager = commandsManager; } @@ -72,10 +69,9 @@ export default class ContextMenuController { event, content: ContextMenu, - // This naming is part of hte uiDialogService convention - // Clicking outside simpy closes the dialog box. - onClickOutside: () => - this.services.uiDialogService.dismiss({ id: 'context-menu' }), + // This naming is part of the uiDialogService convention + // Clicking outside simply closes the dialog box. + onClickOutside: () => this.services.uiDialogService.dismiss({ id: 'context-menu' }), contentProps: { items, @@ -170,9 +166,7 @@ export default class ContextMenuController { }; static _isValidPosition = (source): boolean => { - return ( - source && typeof source.x === 'number' && typeof source.y === 'number' - ); + return source && typeof source.x === 'number' && typeof source.y === 'number'; }; /** @@ -180,10 +174,7 @@ export default class ContextMenuController { */ static _getDefaultPosition = (canvasPoints, eventDetail, viewerElement) => { function* getPositionIterator() { - yield ContextMenuController._getCanvasPointsPosition( - canvasPoints, - viewerElement - ); + yield ContextMenuController._getCanvasPointsPosition(canvasPoints, viewerElement); yield ContextMenuController._getEventDefaultPosition(eventDetail); yield ContextMenuController._getElementDefaultPosition(viewerElement); yield ContextMenuController.getDefaultPosition(); diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js index b5555f71f12..00ee469dd58 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js @@ -1,4 +1,4 @@ -import ContextMenuItemsBuilder from "./ContextMenuItemsBuilder"; +import ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; const menus = [ { diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts index 2fe20e8d96d..8b8b985326f 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts @@ -31,16 +31,11 @@ export function findMenuById(menus: Menu[], menuId?: string): Menu { * @param {*} subProps * @returns */ -export function findMenuDefault( - menus: Menu[], - subProps: Record -): Menu { +export function findMenuDefault(menus: Menu[], subProps: Record): Menu { if (!menus) { return null; } - return menus.find( - menu => !menu.selector || menu.selector(subProps.selectorProps) - ); + return menus.find(menu => !menu.selector || menu.selector(subProps.selectorProps)); } /** @@ -53,11 +48,7 @@ export function findMenuDefault( * @param menuIdFilter - menu id identifier (to be considered on selection) * This is intended to support other types of filtering in the future. */ -export function findMenu( - menus: Menu[], - props?: Types.IProps, - menuIdFilter?: string -) { +export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: string) { const { subMenu } = props; function* findMenuIterator() { @@ -137,10 +128,7 @@ export function getMenuItems( if (!selector || selector(selectorProps)) { if (delegating) { - menuItems = [ - ...menuItems, - ...getMenuItems(selectorProps, event, menus, subMenu), - ]; + menuItems = [...menuItems, ...getMenuItems(selectorProps, event, menus, subMenu)]; } else { const toAdd = adaptItem(item, subProps); menuItems.push(toAdd); @@ -161,10 +149,7 @@ export function getMenuItems( * @returns a MenuItem that is compatible with the base ContextMenu * This requires having a label and set of actions to be called. */ -export function adaptItem( - item: MenuItem, - subProps: ContextMenuProps -): ContextMenuItem { +export function adaptItem(item: MenuItem, subProps: ContextMenuProps): ContextMenuItem { const newItem: ContextMenuItem = { ...item, value: subProps.selectorProps?.value, diff --git a/extensions/default/src/CustomizableContextMenu/types.ts b/extensions/default/src/CustomizableContextMenu/types.ts index 23075d05787..c251fd33d79 100644 --- a/extensions/default/src/CustomizableContextMenu/types.ts +++ b/extensions/default/src/CustomizableContextMenu/types.ts @@ -5,7 +5,7 @@ import { Types } from '@ohif/core'; * menu item for display. * An instance of SelectorProps is provided to the selector functions, which * return true to include the item or false to exclude it. - * The point of this is to allow more specific conext menus which hide + * The point of this is to allow more specific context menus which hide * non-relevant menu options, optimizing the speed of selection of menus */ export interface SelectorProps { @@ -69,7 +69,7 @@ export interface MenuItem { // or more importantly, if the delegating subMenu will be included. selector?: (props: SelectorProps) => boolean; - /** Adapts the item by filling in additional properties as requried */ + /** Adapts the item by filling in additional properties as required */ adaptItem?: (item: MenuItem, props: ContextMenuProps) => UIMenuItem; /** List of commands to run when this item's action is taken. */ diff --git a/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts new file mode 100644 index 00000000000..4e02cafae9f --- /dev/null +++ b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts @@ -0,0 +1,236 @@ +import { ExtensionManager, Types } from '@ohif/core'; + +/** + * This file contains the implementations of BaseDataSourceConfigurationAPIItem + * and BaseDataSourceConfigurationAPI for the Google cloud healthcare API. To + * better understand this implementation and/or to implement custom implementations, + * see the platform\core\src\types\DataSourceConfigurationAPI.ts and its JS doc + * comments as a guide. + */ + +/** + * The various Google Cloud Healthcare path item types. + */ +enum ItemType { + projects = 0, + locations = 1, + datasets = 2, + dicomStores = 3, +} + +interface NamedItem { + name: string; +} +interface Project extends NamedItem { + projectId: string; +} + +const initialUrl = 'https://cloudresourcemanager.googleapis.com/v1'; +const baseHealthcareUrl = 'https://healthcare.googleapis.com/v1'; + +class GoogleCloudDataSourceConfigurationAPIItem + implements Types.BaseDataSourceConfigurationAPIItem +{ + id: string; + name: string; + url: string; + itemType: ItemType; +} + +class GoogleCloudDataSourceConfigurationAPI implements Types.BaseDataSourceConfigurationAPI { + private _extensionManager: ExtensionManager; + private _fetchOptions: { method: string; headers: unknown }; + private _dataSourceName: string; + + constructor(dataSourceName, servicesManager, extensionManager) { + this._dataSourceName = dataSourceName; + this._extensionManager = extensionManager; + const userAuthenticationService = servicesManager.services.userAuthenticationService; + this._fetchOptions = { + method: 'GET', + headers: userAuthenticationService.getAuthorizationHeader(), + }; + } + + getItemLabels = () => ['Project', 'Location', 'Data set', 'DICOM store']; + + async initialize(): Promise { + const url = `${initialUrl}/projects`; + + const projects = (await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + ItemType.projects, + this._fetchOptions + )) as Array; + + if (!projects?.length) { + return []; + } + + const projectItems = projects.map(project => { + return { + id: project.projectId, + name: project.name, + itemType: ItemType.projects, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }; + }); + + return projectItems; + } + + async setCurrentItem( + anItem: Types.BaseDataSourceConfigurationAPIItem + ): Promise { + const googleCloudItem = anItem as GoogleCloudDataSourceConfigurationAPIItem; + + if (googleCloudItem.itemType === ItemType.dicomStores) { + // Last configurable item, so update the data source configuration. + const url = `${googleCloudItem.url}/dicomWeb`; + const dataSourceDefCopy = JSON.parse( + JSON.stringify(this._extensionManager.getDataSourceDefinition(this._dataSourceName)) + ); + dataSourceDefCopy.configuration = { + ...dataSourceDefCopy.configuration, + wadoUriRoot: url, + qidoRoot: url, + wadoRoot: url, + }; + + this._extensionManager.updateDataSourceConfiguration( + dataSourceDefCopy.sourceName, + dataSourceDefCopy.configuration + ); + + return []; + } + + const subItemType = googleCloudItem.itemType + 1; + const subItemField = `${ItemType[subItemType]}`; + + const url = `${googleCloudItem.url}/${subItemField}`; + + const fetchedSubItems = await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + subItemType, + this._fetchOptions + ); + + if (!fetchedSubItems?.length) { + return []; + } + + const subItems = fetchedSubItems.map(subItem => { + const nameSplit = subItem.name.split('/'); + return { + id: subItem.name, + name: nameSplit[nameSplit.length - 1], + itemType: subItemType, + url: `${baseHealthcareUrl}/${subItem.name}`, + }; + }); + + return subItems; + } + + async getConfiguredItems(): Promise> { + const dataSourceDefinition = this._extensionManager.getDataSourceDefinition( + this._dataSourceName + ); + + const url = dataSourceDefinition.configuration.wadoUriRoot; + const projectsIndex = url.indexOf('projects'); + // Split the configured URL into (essentially) pairs (i.e. item type followed by item) + // Explicitly: ['projects','aProject','locations','aLocation','datasets','aDataSet','dicomStores','aDicomStore'] + // Note that a partial configuration will have a subset of the above. + const urlSplit = url.substring(projectsIndex).split('/'); + + const configuredItems = []; + + for ( + let itemType = 0; + // the number of configured items is either the max (4) or the number extracted from the url split + itemType < 4 && (itemType + 1) * 2 < urlSplit.length; + itemType += 1 + ) { + if (itemType === ItemType.projects) { + const projectId = urlSplit[1]; + const projectUrl = `${initialUrl}/projects/${projectId}`; + const data = await GoogleCloudDataSourceConfigurationAPI._doFetch( + projectUrl, + ItemType.projects, + this._fetchOptions + ); + const project = data[0] as Project; + configuredItems.push({ + id: project.projectId, + name: project.name, + itemType: itemType, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }); + } else { + const relativePath = urlSplit.slice(0, itemType * 2 + 2).join('/'); + configuredItems.push({ + id: relativePath, + name: urlSplit[itemType * 2 + 1], + itemType: itemType, + url: `${baseHealthcareUrl}/${relativePath}`, + }); + } + } + + return configuredItems; + } + + /** + * Fetches an array of items the specified item type. + * @param urlStr the fetch url + * @param fetchItemType the type to fetch + * @param fetchOptions the header options for the fetch (e.g. authorization header) + * @param fetchSearchParams any search query params; currently only used for paging results + * @returns an array of items of the specified type + */ + private static async _doFetch( + urlStr: string, + fetchItemType: ItemType, + fetchOptions = {}, + fetchSearchParams: Record = {} + ): Promise | Array> { + try { + const url = new URL(urlStr); + url.search = new URLSearchParams(fetchSearchParams).toString(); + + const response = await fetch(url, fetchOptions); + const data = await response.json(); + if (response.status >= 200 && response.status < 300 && data != null) { + if (data.nextPageToken != null) { + fetchSearchParams.pageToken = data.nextPageToken; + const subPageData = await this._doFetch( + urlStr, + fetchItemType, + fetchOptions, + fetchSearchParams + ); + data[ItemType[fetchItemType]] = data[ItemType[fetchItemType]].concat(subPageData); + } + if (data[ItemType[fetchItemType]]) { + return data[ItemType[fetchItemType]]; + } else if (data.name) { + return [data]; + } else { + return []; + } + } else { + const message = + data?.error?.message || + `Error returned from Google Cloud Healthcare: ${response.status} - ${response.statusText}`; + throw new Error(message); + } + } catch (err) { + const message = err?.message || 'Error occurred during fetch request.'; + throw new Error(message); + } + } +} + +export { GoogleCloudDataSourceConfigurationAPI }; diff --git a/extensions/default/src/DicomJSONDataSource/index.js b/extensions/default/src/DicomJSONDataSource/index.js index 241d430af9e..c4bfb75ce6c 100644 --- a/extensions/default/src/DicomJSONDataSource/index.js +++ b/extensions/default/src/DicomJSONDataSource/index.js @@ -13,6 +13,7 @@ const mappings = { let _store = { urls: [], + studyInstanceUIDMap: new Map(), // map of urls to array of study instance UIDs // { // url: url1 // studies: [Study1, Study2], // if multiple studies @@ -41,11 +42,13 @@ const findStudies = (key, value) => { }; function createDicomJSONApi(dicomJsonConfig) { - const { name, wadoRoot } = dicomJsonConfig; + const { wadoRoot } = dicomJsonConfig; const implementation = { - initialize: async ({ params, query, url }) => { - if (!url) url = query.get('url'); + initialize: async ({ query, url }) => { + if (!url) { + url = query.get('url'); + } let metaData = getMetaDataByURL(url); // if we have already cached the data from this specific url @@ -58,11 +61,7 @@ function createDicomJSONApi(dicomJsonConfig) { } const response = await fetch(url); - let data = await response.json(); - - const studyInstanceUIDs = data.studies.map( - study => study.StudyInstanceUID - ); + const data = await response.json(); let StudyInstanceUID; let SeriesInstanceUID; @@ -89,12 +88,14 @@ function createDicomJSONApi(dicomJsonConfig) { url, studies: [...data.studies], }); - - return studyInstanceUIDs; + _store.studyInstanceUIDMap.set( + url, + data.studies.map(study => study.StudyInstanceUID) + ); }, query: { studies: { - mapParams: () => { }, + mapParams: () => {}, search: async param => { const [key, value] = Object.entries(param)[0]; const mappedParam = mappings[key]; @@ -118,18 +119,18 @@ function createDicomJSONApi(dicomJsonConfig) { }); }, processResults: () => { - console.debug(' DICOMJson QUERY processResults'); + console.warn(' DICOMJson QUERY processResults not implemented'); }, }, series: { // mapParams: mapParams.bind(), search: () => { - console.debug(' DICOMJson QUERY SERIES SEARCH'); + console.warn(' DICOMJson QUERY SERIES SEARCH not implemented'); }, }, instances: { search: () => { - console.debug(' DICOMJson QUERY instances SEARCH'); + console.warn(' DICOMJson QUERY instances SEARCH not implemented'); }, }, }, @@ -151,15 +152,9 @@ function createDicomJSONApi(dicomJsonConfig) { return getDirectURL(wadoRoot, params); }, series: { - metadata: ({ - StudyInstanceUID, - madeInClient = false, - customSort, - } = {}) => { + metadata: async ({ StudyInstanceUID, madeInClient = false, customSort } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0]; @@ -185,16 +180,10 @@ function createDicomJSONApi(dicomJsonConfig) { DicomMetadataStore.addInstances(naturalizedInstances, madeInClient); } - DicomMetadataStore.addSeriesMetadata( - seriesSummaryMetadata, - madeInClient - ); + DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); study.isLoaded = true; } @@ -213,14 +202,16 @@ function createDicomJSONApi(dicomJsonConfig) { return obj; }); storeInstances(instances); - if (index === numberOfSeries - 1) setSuccessFlag(); + if (index === numberOfSeries - 1) { + setSuccessFlag(); + } }); }, }, }, store: { dicom: () => { - console.debug(' DICOMJson store dicom'); + console.warn(' DICOMJson store dicom not implemented'); }, }, getImageIdsForDisplaySet(displaySet) { @@ -252,12 +243,13 @@ function createDicomJSONApi(dicomJsonConfig) { return imageIds; }, getImageIdsForInstance({ instance, frame }) { - const imageIds = getImageId({ - instance, - frame, - }); + const imageIds = getImageId({ instance, frame }); return imageIds; }, + getStudyInstanceUIDs: ({ params, query }) => { + const url = query.get('url'); + return _store.studyInstanceUIDMap.get(url); + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index fc8554b92aa..a7628e80a9c 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -12,8 +12,12 @@ const END_MODALITIES = { }; const compareValue = (v1, v2, def = 0) => { - if (v1 === v2) return def; - if (v1 < v2) return -1; + if (v1 === v2) { + return def; + } + if (v1 < v2) { + return -1; + } return 1; }; @@ -41,25 +45,7 @@ function createDicomLocalApi(dicomLocalConfig) { const { name } = dicomLocalConfig; const implementation = { - initialize: ({ params, query }) => { - const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; - const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs'); - - const StudyInstanceUIDs = - queryStudyInstanceUIDs || paramsStudyInstanceUIDs; - const StudyInstanceUIDsAsArray = - StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) - ? StudyInstanceUIDs - : [StudyInstanceUIDs]; - - // Put SRs at the end of series list to make sure images are loaded first - StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => { - const study = DicomMetadataStore.getStudy(StudyInstanceUID); - study.series = study.series.sort(customSort); - }); - - return StudyInstanceUIDsAsArray; - }, + initialize: ({ params, query }) => {}, query: { studies: { mapParams: () => {}, @@ -99,7 +85,7 @@ function createDicomLocalApi(dicomLocalConfig) { }); }, processResults: () => { - console.debug(' DICOMLocal QUERY processResults'); + console.warn(' DICOMLocal QUERY processResults not implemented'); }, }, series: { @@ -121,7 +107,7 @@ function createDicomLocalApi(dicomLocalConfig) { }, instances: { search: () => { - console.debug(' DICOMLocal QUERY instances SEARCH'); + console.warn(' DICOMLocal QUERY instances SEARCH not implemented'); }, }, }, @@ -141,16 +127,11 @@ function createDicomLocalApi(dicomLocalConfig) { series: { metadata: async ({ StudyInstanceUID, madeInClient = false } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } // Instances metadata already added via local upload - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); // Series metadata already added via local upload DicomMetadataStore._broadcastEvent(EVENTS.SERIES_ADDED, { @@ -246,6 +227,28 @@ function createDicomLocalApi(dicomLocalConfig) { deleteStudyMetadataPromise() { console.log('deleteStudyMetadataPromise not implemented'); }, + getStudyInstanceUIDs: ({ params, query }) => { + const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; + const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs'); + + const StudyInstanceUIDs = queryStudyInstanceUIDs || paramsStudyInstanceUIDs; + const StudyInstanceUIDsAsArray = + StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) + ? StudyInstanceUIDs + : [StudyInstanceUIDs]; + + // Put SRs at the end of series list to make sure images are loaded first + let isStudyInCache = false; + StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => { + const study = DicomMetadataStore.getStudy(StudyInstanceUID); + if (study) { + study.series = study.series.sort(customSort); + isStudyInCache = true; + } + }); + + return isStudyInCache ? StudyInstanceUIDsAsArray : []; + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css index fe6a9609631..2845f2f39c1 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css @@ -4,7 +4,7 @@ } .dicom-tag-browser-table-wrapper { -/* height: 500px;*/ + /* height: 500px;*/ /*overflow-y: scroll;*/ overflow-x: scroll; } @@ -45,7 +45,7 @@ padding-left: 10px; padding-right: 10px; text-align: center; - color: "#20A5D6"; + color: '#20A5D6'; } .dicom-tag-browser-table th.dicom-tag-browser-table-left { diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx index 57cf04fa83c..22eea9bc85a 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx @@ -1,10 +1,9 @@ import dcmjs from 'dcmjs'; import moment from 'moment'; -import React, { useState, useMemo, useEffect, useRef } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { classes } from '@ohif/core'; -import { Icon, InputRange, Select, Typography } from '@ohif/ui'; +import { InputRange, Select, Typography, InputFilterText } from '@ohif/ui'; import debounce from 'lodash.debounce'; -import classNames from 'classnames'; import DicomTagTable from './DicomTagTable'; import './DicomTagBrowser.css'; @@ -22,10 +21,8 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { // 3: Value const excludedColumnIndicesForFilter: Set = new Set([1]); - const [ - selectedDisplaySetInstanceUID, - setSelectedDisplaySetInstanceUID, - ] = useState(displaySetInstanceUID); + const [selectedDisplaySetInstanceUID, setSelectedDisplaySetInstanceUID] = + useState(displaySetInstanceUID); const [instanceNumber, setInstanceNumber] = useState(1); const [filterValue, setFilterValue] = useState(''); @@ -34,8 +31,6 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { setInstanceNumber(1); }; - const searchInputRef = useRef(null); - const activeDisplaySet = displaySets.find( ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID ); @@ -113,27 +108,31 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { return (
-
-
- +
+
+ Series -
+
debouncedSetFilterValue(event.target.value)} - autoComplete="off" - > - - { - searchInputRef.current.value = ''; - debouncedSetFilterValue(''); - }} - > - - +
+
+
@@ -196,24 +173,14 @@ function getFormattedRowsFromTags(tags, metadata) { tags.forEach(tagInfo => { if (tagInfo.vr === 'SQ') { - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - '', - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, '']); const { values } = tagInfo; values.forEach((item, index) => { const formatedRowsFromTags = getFormattedRowsFromTags(item, metadata); - rows.push([ - `${item[0].tagIndent}(FFFE,E000)`, - '', - `Item #${index}`, - '', - ]); + rows.push([`${item[0].tagIndent}(FFFE,E000)`, '', `Item #${index}`, '']); rows.push(...formatedRowsFromTags); }); @@ -224,17 +191,10 @@ function getFormattedRowsFromTags(tags, metadata) { const originalTagInfo = metadata[tag]; tagInfo.vr = originalTagInfo.vr; } catch (error) { - console.error( - `Failed to parse value representation for tag '${tagInfo.keyword}'` - ); + console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`); } } - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - tagInfo.value, - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, tagInfo.value]); } }); diff --git a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx index 982af013b48..a661da9f7bb 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx @@ -17,48 +17,40 @@ function ColumnHeaders({ tagRef, vrRef, keywordRef, valueRef }) { return (
-
+
-
+
-
+
-
+
@@ -114,10 +106,7 @@ function DicomTagTable({ rows }) { * When the browser window resizes, update the row virtualization (i.e. row heights) */ useEffect(() => { - const debouncedResize = debounce( - () => listRef.current.resetAfterIndex(0), - 100 - ); + const debouncedResize = debounce(() => listRef.current.resetAfterIndex(0), 100); window.addEventListener('resize', debouncedResize); @@ -135,15 +124,15 @@ function DicomTagTable({ rows }) {
-
{row[0]}
-
{row[1]}
-
{row[2]}
-
{row[3]}
+
{row[0]}
+
{row[1]}
+
{row[2]}
+
{row[3]}
); }, @@ -154,9 +143,7 @@ function DicomTagTable({ rows }) { * Whenever any one of the column headers is set, then the header is rendered. * Here we chose the tag header. */ - const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [ - tagHeaderElem, - ]); + const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [tagHeaderElem]); /** * Get the item/row size. We use the header column widths to calculate the various row heights. @@ -179,11 +166,7 @@ function DicomTagTable({ rows }) { .map((colText, index) => { const colOneLineWidth = context.measureText(colText).width; const numLines = Math.ceil(colOneLineWidth / headerWidths[index]); - return ( - numLines * lineHeightPx + - 2 * rowVerticalPaddingPx + - rowBottomBorderPx - ); + return numLines * lineHeightPx + 2 * rowVerticalPaddingPx + rowBottomBorderPx; }) .reduce((maxHeight, colHeight) => Math.max(maxHeight, colHeight)); }, @@ -204,7 +187,7 @@ function DicomTagTable({ rows }) { valueRef={valueRef} />
{isHeaderRendered() && ( diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 00f82841ed6..b265e4a07a9 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -1,11 +1,5 @@ import { api } from 'dicomweb-client'; -import { - DicomMetadataStore, - IWebApiDataSource, - utils, - errorHandler, - classes, -} from '@ohif/core'; +import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler, classes } from '@ohif/core'; import { mapParams, @@ -18,10 +12,7 @@ import dcm4cheeReject from './dcm4cheeReject'; import getImageId from './utils/getImageId'; import dcmjs from 'dcmjs'; -import { - retrieveStudyMetadata, - deleteStudyMetadataPromise, -} from './retrieveStudyMetadata.js'; +import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStudyMetadata.js'; import StaticWadoClient from './utils/StaticWadoClient'; import getDirectURL from '../utils/getDirectURL'; import { fixBulkDataURI } from './utils/fixBulkDataURI'; @@ -30,8 +21,7 @@ const { DicomMetaDictionary, DicomDict } = dcmjs.data; const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary; -const ImplementationClassUID = - '2.25.270695996825855179949881587723571202391.2.0.0'; +const ImplementationClassUID = '2.25.270695996825855179949881587723571202391.2.0.0'; const ImplementationVersionName = 'OHIF-VIEWER-2.0.0'; const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1'; @@ -51,82 +41,87 @@ const metadataProvider = classes.MetadataProvider; * @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true */ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { - const { - qidoRoot, - wadoRoot, - enableStudyLazyLoad, - supportsFuzzyMatching, - supportsWildcard, - supportsReject, - staticWado, - singlepart, - } = dicomWebConfig; - - const dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig)); - - const qidoConfig = { - url: qidoRoot, - staticWado, - singlepart, - headers: userAuthenticationService.getAuthorizationHeader(), - errorInterceptor: errorHandler.getHTTPErrorHandler(), - }; + let dicomWebConfigCopy, + qidoConfig, + wadoConfig, + qidoDicomWebClient, + wadoDicomWebClient, + getAuthrorizationHeader, + generateWadoHeader; - const wadoConfig = { - url: wadoRoot, - staticWado, - singlepart, - headers: userAuthenticationService.getAuthorizationHeader(), - errorInterceptor: errorHandler.getHTTPErrorHandler(), - }; + const implementation = { + initialize: ({ params, query }) => { + if (dicomWebConfig.onConfiguration && typeof dicomWebConfig.onConfiguration === 'function') { + dicomWebConfig = dicomWebConfig.onConfiguration(dicomWebConfig, { + params, + query, + }); + } - // TODO -> Two clients sucks, but its better than 1000. - // TODO -> We'll need to merge auth later. - const qidoDicomWebClient = staticWado - ? new StaticWadoClient(qidoConfig) - : new api.DICOMwebClient(qidoConfig); + dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig)); - const wadoDicomWebClient = staticWado - ? new StaticWadoClient(wadoConfig) - : new api.DICOMwebClient(wadoConfig); + getAuthrorizationHeader = () => { + const xhrRequestHeaders = {}; + const authHeaders = userAuthenticationService.getAuthorizationHeader(); + if (authHeaders && authHeaders.Authorization) { + xhrRequestHeaders.Authorization = authHeaders.Authorization; + } + return xhrRequestHeaders; + }; - const implementation = { - initialize: ({ params, query }) => { - const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; - const queryStudyInstanceUIDs = utils.splitComma( - query.getAll('StudyInstanceUIDs') - ); + generateWadoHeader = () => { + let authorizationHeader = getAuthrorizationHeader(); + //Generate accept header depending on config params + let formattedAcceptHeader = utils.generateAcceptHeader( + dicomWebConfig.acceptHeader, + dicomWebConfig.requestTransferSyntaxUID, + dicomWebConfig.omitQuotationForMultipartRequest + ); - const StudyInstanceUIDs = - (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || - paramsStudyInstanceUIDs; - const StudyInstanceUIDsAsArray = - StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) - ? StudyInstanceUIDs - : [StudyInstanceUIDs]; - return StudyInstanceUIDsAsArray; + return { + ...authorizationHeader, + Accept: formattedAcceptHeader, + }; + }; + + qidoConfig = { + url: dicomWebConfig.qidoRoot, + staticWado: dicomWebConfig.staticWado, + singlepart: dicomWebConfig.singlepart, + headers: userAuthenticationService.getAuthorizationHeader(), + errorInterceptor: errorHandler.getHTTPErrorHandler(), + }; + + wadoConfig = { + url: dicomWebConfig.wadoRoot, + staticWado: dicomWebConfig.staticWado, + singlepart: dicomWebConfig.singlepart, + headers: userAuthenticationService.getAuthorizationHeader(), + errorInterceptor: errorHandler.getHTTPErrorHandler(), + }; + + // TODO -> Two clients sucks, but its better than 1000. + // TODO -> We'll need to merge auth later. + qidoDicomWebClient = dicomWebConfig.staticWado + ? new StaticWadoClient(qidoConfig) + : new api.DICOMwebClient(qidoConfig); + + wadoDicomWebClient = dicomWebConfig.staticWado + ? new StaticWadoClient(wadoConfig) + : new api.DICOMwebClient(wadoConfig); }, query: { studies: { mapParams: mapParams.bind(), - search: async function(origParams) { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - + search: async function (origParams) { + qidoDicomWebClient.headers = getAuthrorizationHeader(); const { studyInstanceUid, seriesInstanceUid, ...mappedParams } = mapParams(origParams, { - supportsFuzzyMatching, - supportsWildcard, + supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching, + supportsWildcard: dicomWebConfig.supportsWildcard, }) || {}; - const results = await qidoSearch( - qidoDicomWebClient, - undefined, - undefined, - mappedParams - ); + const results = await qidoSearch(qidoDicomWebClient, undefined, undefined, mappedParams); return processResults(results); }, @@ -134,16 +129,9 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, series: { // mapParams: mapParams.bind(), - search: async function(studyInstanceUid) { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - - const results = await seriesInStudy( - qidoDicomWebClient, - studyInstanceUid - ); + search: async function (studyInstanceUid) { + qidoDicomWebClient.headers = getAuthrorizationHeader(); + const results = await seriesInStudy(qidoDicomWebClient, studyInstanceUid); return processSeriesResults(results); }, @@ -151,18 +139,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, instances: { search: (studyInstanceUid, queryParameters) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - - qidoSearch.call( - undefined, - qidoDicomWebClient, - studyInstanceUid, - null, - queryParameters - ); + qidoDicomWebClient.headers = getAuthrorizationHeader(); + qidoSearch.call(undefined, qidoDicomWebClient, studyInstanceUid, null, queryParameters); }, }, }, @@ -179,9 +157,16 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { * or is already retrieved, or a promise to a URL for such use if a BulkDataURI */ directURL: params => { - return getDirectURL({ wadoRoot, singlepart }, params); + return getDirectURL( + { + wadoRoot: dicomWebConfig.wadoRoot, + singlepart: dicomWebConfig.singlepart, + }, + params + ); }, bulkDataURI: async ({ StudyInstanceUID, BulkDataURI }) => { + qidoDicomWebClient.headers = getAuthrorizationHeader(); const options = { multipart: false, BulkDataURI, @@ -200,18 +185,11 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { sortFunction, madeInClient = false, } = {}) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - wadoDicomWebClient.headers = headers; - } - if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } - if (enableStudyLazyLoad) { + if (dicomWebConfig.enableStudyLazyLoad) { return implementation._retrieveSeriesMetadataAsync( StudyInstanceUID, filters, @@ -234,22 +212,16 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { store: { dicom: async (dataset, request) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - wadoDicomWebClient.headers = headers; - } - + wadoDicomWebClient.headers = getAuthrorizationHeader(); if (dataset instanceof ArrayBuffer) { const options = { datasets: [dataset], request, }; - await wadoDicomWebClient.storeInstances(options); } else { const meta = { - FileMetaInformationVersion: - dataset._meta.FileMetaInformationVersion.Value, + FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value, MediaStorageSOPClassUID: dataset.SOPClassUID, MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, @@ -282,7 +254,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { madeInClient ) => { const enableStudyLazyLoad = false; - + wadoDicomWebClient.headers = generateWadoHeader(); // data is all SOPInstanceUIDs const data = await retrieveStudyMetadata( wadoDicomWebClient, @@ -323,6 +295,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }); instance.imageId = imageId; + instance.wadoRoot = dicomWebConfig.wadoRoot; + instance.wadoUri = dicomWebConfig.wadoUri; metadataProvider.addImageIdToUIDs(imageId, { StudyInstanceUID, @@ -338,10 +312,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { DicomMetadataStore.addSeriesMetadata(seriesMetadata, madeInClient); Object.keys(instancesPerSeries).forEach(seriesInstanceUID => - DicomMetadataStore.addInstances( - instancesPerSeries[seriesInstanceUID], - madeInClient - ) + DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient) ); }, @@ -353,18 +324,17 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { madeInClient = false ) => { const enableStudyLazyLoad = true; + wadoDicomWebClient.headers = generateWadoHeader(); // Get Series - const { - preLoadData: seriesSummaryMetadata, - promises: seriesPromises, - } = await retrieveStudyMetadata( - wadoDicomWebClient, - StudyInstanceUID, - enableStudyLazyLoad, - filters, - sortCriteria, - sortFunction - ); + const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } = + await retrieveStudyMetadata( + wadoDicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ); /** * naturalizes the dataset, and adds a retrieve bulkdata method @@ -375,7 +345,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const addRetrieveBulkData = instance => { const naturalized = naturalizeDataset(instance); - // if we konw the server doesn't use bulkDataURI, then don't + // if we know the server doesn't use bulkDataURI, then don't if (!dicomWebConfig.bulkDataURI?.enabled) { return naturalized; } @@ -409,8 +379,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // the bulk data and DICOM video cases where the second ArrayBuffer is // the bulk data. Here we play it safe and do a find. const ret = - (val instanceof Array && - val.find(arrayBuffer => arrayBuffer?.byteLength)) || + (val instanceof Array && val.find(arrayBuffer => arrayBuffer?.byteLength)) || undefined; value.Value = ret; return ret; @@ -453,10 +422,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { } function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); study.isLoaded = true; } @@ -515,10 +481,23 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { getConfig() { return dicomWebConfigCopy; }, + getStudyInstanceUIDs({ params, query }) { + const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; + const queryStudyInstanceUIDs = utils.splitComma(query.getAll('StudyInstanceUIDs')); + + const StudyInstanceUIDs = + (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || paramsStudyInstanceUIDs; + const StudyInstanceUIDsAsArray = + StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) + ? StudyInstanceUIDs + : [StudyInstanceUIDs]; + + return StudyInstanceUIDsAsArray; + }, }; - if (supportsReject) { - implementation.reject = dcm4cheeReject(wadoRoot); + if (dicomWebConfig.supportsReject) { + implementation.reject = dcm4cheeReject(dicomWebConfig.wadoRoot); } return IWebApiDataSource.create(implementation); diff --git a/extensions/default/src/DicomWebDataSource/qido.js b/extensions/default/src/DicomWebDataSource/qido.js index ce372cd5fc6..4bea9a07472 100644 --- a/extensions/default/src/DicomWebDataSource/qido.js +++ b/extensions/default/src/DicomWebDataSource/qido.js @@ -54,10 +54,7 @@ function processResults(qidoStudies) { patientName: utils.formatPN(getName(qidoStudy['00100010'])) || '', instances: Number(getString(qidoStudy['00201208'])) || 0, // number description: getString(qidoStudy['00081030']) || '', - modalities: - getString( - getModalities(qidoStudy['00080060'], qidoStudy['00080061']) - ) || '', + modalities: getString(getModalities(qidoStudy['00080060'], qidoStudy['00080061'])) || '', }) ); @@ -105,12 +102,7 @@ export function processSeriesResults(qidoSeries) { * @param {string} [queryParamaters] * @returns {Promise} - Promise that resolves results */ -async function search( - dicomWebClient, - studyInstanceUid, - seriesInstanceUid, - queryParameters -) { +async function search(dicomWebClient, studyInstanceUid, seriesInstanceUid, queryParameters) { let searchResult = await dicomWebClient.searchForStudies({ studyInstanceUid: undefined, queryParams: queryParameters, @@ -136,10 +128,7 @@ export function seriesInStudy(dicomWebClient, studyInstanceUID) { } export default function searchStudies(server, filter) { - const queryParams = getQIDOQueryParams( - filter, - server.qidoSupportsIncludeField - ); + const queryParams = getQIDOQueryParams(filter, server.qidoSupportsIncludeField); const options = { queryParams, }; diff --git a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js index faf5e2c9010..e4882d05e84 100644 --- a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js +++ b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js @@ -9,7 +9,7 @@ const StudyMetaDataPromises = new Map(); * * @param {Object} server Object with server configuration parameters * @param {string} StudyInstanceUID The UID of the Study to be retrieved - * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronusly. + * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronously. * @param {function} storeInstancesCallback A callback used to store the retrieved instance metadata. * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against @@ -28,14 +28,10 @@ export function retrieveStudyMetadata( // corresponding promise from the "StudyMetaDataPromises" map... if (!dicomWebClient) { - throw new Error( - `${moduleName}: Required 'dicomWebClient' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'dicomWebClient' parameter not provided.`); } if (!StudyInstanceUID) { - throw new Error( - `${moduleName}: Required 'StudyInstanceUID' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`); } // Already waiting on result? Return cached promise @@ -52,7 +48,7 @@ export function retrieveStudyMetadata( filters, sortCriteria, sortFunction - ).then(function(data) { + ).then(function (data) { resolve(data); }, reject); }); diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts index 9521e379b09..1ba2aad9069 100644 --- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts @@ -36,24 +36,21 @@ export default class StaticWadoClient extends api.DICOMwebClient { * @returns */ async searchForStudies(options) { - if (!this.staticWado) return super.searchForStudies(options); + if (!this.staticWado) { + return super.searchForStudies(options); + } const searchResult = await super.searchForStudies(options); const { queryParams } = options; - if (!queryParams) return searchResult; + if (!queryParams) { + return searchResult; + } const lowerParams = this.toLowerParams(queryParams); const filtered = searchResult.filter(study => { for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - study, - StaticWadoClient.studyFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, study, StaticWadoClient.studyFilterKeys)) { return false; } } @@ -63,23 +60,20 @@ export default class StaticWadoClient extends api.DICOMwebClient { } async searchForSeries(options) { - if (!this.staticWado) return super.searchForSeries(options); + if (!this.staticWado) { + return super.searchForSeries(options); + } const searchResult = await super.searchForSeries(options); const { queryParams } = options; - if (!queryParams) return searchResult; + if (!queryParams) { + return searchResult; + } const lowerParams = this.toLowerParams(queryParams); const filtered = searchResult.filter(series => { for (const key of Object.keys(StaticWadoClient.seriesFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - series, - StaticWadoClient.seriesFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, series, StaticWadoClient.seriesFilterKeys)) { return false; } } @@ -112,18 +106,19 @@ export default class StaticWadoClient extends api.DICOMwebClient { actual = actual.Alphabetic; } if (typeof actual == 'string') { - if (actual.length === 0) return true; - if (desired.length === 0 || desired === '*') return true; + if (actual.length === 0) { + return true; + } + if (desired.length === 0 || desired === '*') { + return true; + } if (desired[0] === '*' && desired[desired.length - 1] === '*') { // console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`) return actual.indexOf(desired.substring(1, desired.length - 1)) != -1; } else if (desired[desired.length - 1] === '*') { return actual.indexOf(desired.substring(0, desired.length - 1)) != -1; } else if (desired[0] === '*') { - return ( - actual.indexOf(desired.substring(1)) === - actual.length - desired.length + 1 - ); + return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1; } } return desired === actual; @@ -131,9 +126,13 @@ export default class StaticWadoClient extends api.DICOMwebClient { /** Compares a pair of dates to see if the value is within the range */ compareDateRange(range, value) { - if (!value) return true; + if (!value) { + return true; + } const dash = range.indexOf('-'); - if (dash === -1) return this.compareValues(range, value); + if (dash === -1) { + return this.compareValues(range, value); + } const start = range.substring(0, dash); const end = range.substring(dash + 1); return (!start || value >= start) && (!end || value <= end); @@ -150,12 +149,18 @@ export default class StaticWadoClient extends api.DICOMwebClient { */ filterItem(key: string, queryParams, study, sourceFilterMap) { const altKey = sourceFilterMap[key] || key; - if (!queryParams) return true; + if (!queryParams) { + return true; + } const testValue = queryParams[key] || queryParams[altKey]; - if (!testValue) return true; + if (!testValue) { + return true; + } const valueElem = study[key] || study[altKey]; - if (!valueElem) return false; - if (valueElem.vr == 'DA') { + if (!valueElem) { + return false; + } + if (valueElem.vr === 'DA' && valueElem.Value?.[0]) { return this.compareDateRange(testValue, valueElem.Value[0]); } const value = valueElem.Value; diff --git a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts index 1a408b597fe..2e592c75700 100644 --- a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts +++ b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts @@ -20,10 +20,7 @@ function fixBulkDataURI(value, instance, dicomWebConfig) { // in case of the relative path, make it absolute. The current DICOM standard says // the bulkdataURI is relative to the series. However, there are situations where // it can be relative to the study too - if ( - !value.BulkDataURI.startsWith('http') && - !value.BulkDataURI.startsWith('/') - ) { + if (!value.BulkDataURI.startsWith('http') && !value.BulkDataURI.startsWith('/')) { if (dicomWebConfig.bulkDataURI?.relativeResolution === 'studies') { value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${instance.StudyInstanceUID}/${value.BulkDataURI}`; } else if ( diff --git a/extensions/default/src/DicomWebDataSource/utils/getImageId.js b/extensions/default/src/DicomWebDataSource/utils/getImageId.js index a493a3d3ad1..29877bd432c 100644 --- a/extensions/default/src/DicomWebDataSource/utils/getImageId.js +++ b/extensions/default/src/DicomWebDataSource/utils/getImageId.js @@ -24,12 +24,7 @@ function buildInstanceWadoUrl(config, instance) { * @param thumbnail * @returns {string} The imageId to be used by Cornerstone */ -export default function getImageId({ - instance, - frame, - config, - thumbnail = false, -}) { +export default function getImageId({ instance, frame, config, thumbnail = false }) { if (!instance) { return; } diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js index d70a2cdccd0..ba62cca1da8 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js @@ -20,9 +20,7 @@ async function RetrieveMetadata( sortFunction ) { const RetrieveMetadataLoader = - enableStudyLazyLoad !== false - ? RetrieveMetadataLoaderAsync - : RetrieveMetadataLoaderSync; + enableStudyLazyLoad !== false ? RetrieveMetadataLoaderAsync : RetrieveMetadataLoaderSync; const retrieveMetadataLoader = new RetrieveMetadataLoader( dicomWebClient, diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js index 49302f7a835..da747c88bbe 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js @@ -14,13 +14,7 @@ export default class RetrieveMetadataLoader { * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against * @param {Function} [sortSeries] - Custom sort function for series */ - constructor( - client, - studyInstanceUID, - filters = {}, - sortCriteria, - sortFunction - ) { + constructor(client, studyInstanceUID, filters = {}, sortCriteria, sortFunction) { this.client = client; this.studyInstanceUID = studyInstanceUID; this.filters = filters; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index 096c3c8183f..bca30942929 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -1,8 +1,5 @@ import dcmjs from 'dcmjs'; -import { - sortStudySeries, - sortingCriteria, -} from '@ohif/core/src/utils/sortStudy'; +import { sortStudySeries, sortingCriteria } from '@ohif/core/src/utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; /** @@ -12,11 +9,7 @@ import RetrieveMetadataLoader from './retrieveMetadataLoader'; * @param {Array} seriesInstanceUIDList A list of Series Instance UIDs * @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID */ -function makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDList -) { +function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) { return Object.freeze({ hasNext() { return seriesInstanceUIDList.length > 0; @@ -43,11 +36,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader */ *getPreLoaders() { const preLoaders = []; - const { - studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; if (seriesInstanceUID) { const options = { @@ -73,8 +62,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader return sortStudySeries( naturalized, - sortCriteria || - sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, + sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, sortFunction ); } @@ -84,11 +72,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID); - const seriesAsyncLoader = makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDs - ); + const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs); const promises = []; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js index 6ca47ce9db2..1a7cd9d500b 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js @@ -31,11 +31,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { */ *getLoaders() { const loaders = []; - const { - studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; if (seriesInstanceUID) { loaders.push( @@ -46,9 +42,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { ); } - loaders.push( - client.retrieveStudyMetadata.bind(client, { studyInstanceUID }) - ); + loaders.push(client.retrieveStudyMetadata.bind(client, { studyInstanceUID })); yield* loaders; } diff --git a/extensions/default/src/DicomWebProxyDataSource/index.js b/extensions/default/src/DicomWebProxyDataSource/index.js index e873237dde4..ab1d8e49e31 100644 --- a/extensions/default/src/DicomWebProxyDataSource/index.js +++ b/extensions/default/src/DicomWebProxyDataSource/index.js @@ -9,24 +9,12 @@ import { createDicomWebApi } from '../DicomWebDataSource/index'; * dicomWeb configuration array * */ -function createDicomWebProxyApi( - dicomWebProxyConfig, - UserAuthenticationService -) { +function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) { const { name } = dicomWebProxyConfig; let dicomWebDelegate = undefined; const implementation = { initialize: async ({ params, query }) => { - let studyInstanceUIDs = []; - - // there seem to be a couple of variations of the case for this parameter - const queryStudyInstanceUIDs = - query.get('studyInstanceUIDs') || query.get('studyInstanceUids'); - if (!queryStudyInstanceUIDs) { - throw new Error(`No studyInstanceUids in request for '${name}'`); - } - const url = query.get('url'); if (!url) { @@ -39,12 +27,11 @@ function createDicomWebProxyApi( } dicomWebDelegate = createDicomWebApi( - data.servers.dicomWeb[0], + data.servers.dicomWeb[0].configuration, UserAuthenticationService ); - studyInstanceUIDs = queryStudyInstanceUIDs.split(';'); + dicomWebDelegate.initialize({ params, query }); } - return studyInstanceUIDs; }, query: { studies: { @@ -55,28 +42,33 @@ function createDicomWebProxyApi( }, instances: { search: (studyInstanceUid, queryParameters) => - dicomWebDelegate.query.instances.search( - studyInstanceUid, - queryParameters - ), + dicomWebDelegate.query.instances.search(studyInstanceUid, queryParameters), }, }, retrieve: { directURL: (...args) => dicomWebDelegate.retrieve.directURL(...args), series: { - metadata: (...args) => - dicomWebDelegate.retrieve.series.metadata(...args), + metadata: async (...args) => dicomWebDelegate.retrieve.series.metadata(...args), }, }, store: { dicom: (...args) => dicomWebDelegate.store(...args), }, - deleteStudyMetadataPromise: (...args) => - dicomWebDelegate.deleteStudyMetadataPromise(...args), - getImageIdsForDisplaySet: (...args) => - dicomWebDelegate.getImageIdsForDisplaySet(...args), - getImageIdsForInstance: (...args) => - dicomWebDelegate.getImageIdsForInstance(...args), + deleteStudyMetadataPromise: (...args) => dicomWebDelegate.deleteStudyMetadataPromise(...args), + getImageIdsForDisplaySet: (...args) => dicomWebDelegate.getImageIdsForDisplaySet(...args), + getImageIdsForInstance: (...args) => dicomWebDelegate.getImageIdsForInstance(...args), + getStudyInstanceUIDs({ params, query }) { + let studyInstanceUIDs = []; + + // there seem to be a couple of variations of the case for this parameter + const queryStudyInstanceUIDs = + query.get('studyInstanceUIDs') || query.get('studyInstanceUids'); + if (!queryStudyInstanceUIDs) { + throw new Error(`No studyInstanceUids in request for '${name}'`); + } + studyInstanceUIDs = queryStudyInstanceUIDs.split(';'); + return studyInstanceUIDs; + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/Panels/ActionButtons.tsx b/extensions/default/src/Panels/ActionButtons.tsx index f372a1ee086..c21f8b6b652 100644 --- a/extensions/default/src/Panels/ActionButtons.tsx +++ b/extensions/default/src/Panels/ActionButtons.tsx @@ -2,21 +2,31 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { Button, ButtonGroup } from '@ohif/ui'; +import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; function ActionButtons({ onExportClick, onCreateReportClick }) { const { t } = useTranslation('MeasurementTable'); return ( - - - - + + ); } diff --git a/extensions/default/src/Panels/DataSourceSelector.tsx b/extensions/default/src/Panels/DataSourceSelector.tsx index decb24ad83d..dcb62c28399 100644 --- a/extensions/default/src/Panels/DataSourceSelector.tsx +++ b/extensions/default/src/Panels/DataSourceSelector.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames'; import { useNavigate } from 'react-router-dom'; import { useAppConfig } from '@state'; -import { Button } from '@ohif/ui'; +import { Button, ButtonEnums } from '@ohif/ui'; function DataSourceSelector() { const [appConfig] = useAppConfig(); @@ -15,25 +15,24 @@ function DataSourceSelector() { return (
-
-
+
+
OHIF -
+
{dsConfigs - .filter( - it => - it.sourceName !== 'dicomjson' && - it.sourceName !== 'dicomlocal' - ) + .filter(it => it.sourceName !== 'dicomjson' && it.sourceName !== 'dicomlocal') .map(ds => (
-

{ds.friendlyName}

+

+ {ds.configuration?.friendlyName || ds.friendlyName} +

+
} diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx index 415b80015e4..628cb14eab3 100644 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Button, ButtonGroup } from '@ohif/ui'; +import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; import { useTranslation } from 'react-i18next'; function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { @@ -8,9 +8,13 @@ function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { return ( <> {segmentations?.length ? ( -
- - - - - - + +
) : null} diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx index a18dee1c2d7..306c39c9461 100644 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx @@ -5,9 +5,7 @@ import { SegmentationTable, Button, Icon } from '@ohif/ui'; import { useTranslation } from 'react-i18next'; import segmentationEditHandler from './segmentationEditHandler'; import ExportReports from './ExportReports'; -import ROIThresholdConfiguration, { - ROI_STAT, -} from './ROIThresholdConfiguration'; +import ROIThresholdConfiguration, { ROI_STAT } from './ROIThresholdConfiguration'; const LOWER_CT_THRESHOLD_DEFAULT = -1024; const UPPER_CT_THRESHOLD_DEFAULT = 1024; @@ -44,19 +42,14 @@ function reducer(state, action) { } } -export default function PanelRoiThresholdSegmentation({ - servicesManager, - commandsManager, -}) { +export default function PanelRoiThresholdSegmentation({ servicesManager, commandsManager }) { const { segmentationService } = servicesManager.services; const { t } = useTranslation('PanelSUV'); const [showConfig, setShowConfig] = useState(false); const [labelmapLoading, setLabelmapLoading] = useState(false); const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); const [config, dispatch] = useReducer(reducer, { strategy: DEFAULT_STRATEGY, @@ -95,9 +88,7 @@ export default function PanelRoiThresholdSegmentation({ const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue; // update segDetails with the suv peak for the active segmentation - const segmentation = segmentationService.getSegmentation( - selectedSegmentationId - ); + const segmentation = segmentationService.getSegmentation(selectedSegmentationId); const cachedStats = { lesionStats, @@ -178,10 +169,9 @@ export default function PanelRoiThresholdSegmentation({ return ( <>
-
-
+
+
- +
{ setShowConfig(!showConfig); }} > -
- {t('ROI Threshold Configuration')} -
+
{t('ROI Threshold Configuration')}
{showConfig && ( {tmtvValue !== null ? ( -
- +
+ {'TMTV:'}
{`${tmtvValue} mL`}
@@ -265,13 +251,10 @@ export default function PanelRoiThresholdSegmentation({
{ // navigate to a url in a new tab - window.open( - 'https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', - '_blank' - ); + window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank'); }} > +
-
+
)} {config.strategy !== ROI_STAT && ( -
+
- - -
+
- + +
- + +
- -
+ ); }, actions: [ - // temp: swap button types until colors are updated - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Save', type: 'secondary' }, + { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, + { id: 'save', text: 'Save', type: ButtonEnums.type.primary }, ], onSubmit: onSubmitHandler, }, diff --git a/extensions/tmtv/src/commandsModule.js b/extensions/tmtv/src/commandsModule.js index b633d231cc9..73409ee411a 100644 --- a/extensions/tmtv/src/commandsModule.js +++ b/extensions/tmtv/src/commandsModule.js @@ -14,11 +14,7 @@ const metadataProvider = classes.MetadataProvider; const RECTANGLE_ROI_THRESHOLD_MANUAL = 'RectangleROIStartEndThreshold'; const LABELMAP = csTools.Enums.SegmentationRepresentations.Labelmap; -const commandsModule = ({ - servicesManager, - commandsManager, - extensionManager, -}) => { +const commandsModule = ({ servicesManager, commandsManager, extensionManager }) => { const { viewportGridService, uiNotificationService, @@ -36,8 +32,8 @@ const commandsModule = ({ const { getEnabledElement } = utilityModule.exports; function _getActiveViewportsEnabledElement() { - const { activeViewportIndex } = viewportGridService.getState(); - const { element } = getEnabledElement(activeViewportIndex) || {}; + const { activeViewportId } = viewportGridService.getState(); + const { element } = getEnabledElement(activeViewportId) || {}; const enabledElement = cs.getEnabledElement(element); return enabledElement; } @@ -45,8 +41,8 @@ const commandsModule = ({ function _getMatchedViewportsToolGroupIds() { const { viewportMatchDetails } = hangingProtocolService.getMatchDetails(); const toolGroupIds = []; - viewportMatchDetails.forEach((value, key) => { - const { viewportOptions } = value; + viewportMatchDetails.forEach(viewport => { + const { viewportOptions } = viewport; const { toolGroupId } = viewportOptions; if (toolGroupIds.indexOf(toolGroupId) === -1) { toolGroupIds.push(toolGroupId); @@ -64,7 +60,7 @@ const commandsModule = ({ // corrected PT vs the non-attenuation correct PT) let ptDisplaySet = null; - for (const [viewportIndex, viewportDetails] of viewportMatchDetails) { + for (const [viewportId, viewportDetails] of viewportMatchDetails) { const { displaySetsInfo } = viewportDetails; const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) => displaySetService.getDisplaySetByUID(displaySetInstanceUID) @@ -74,9 +70,7 @@ const commandsModule = ({ continue; } - ptDisplaySet = displaySets.find( - displaySet => displaySet.Modality === 'PT' - ); + ptDisplaySet = displaySets.find(displaySet => displaySet.Modality === 'PT'); if (ptDisplaySet) { break; @@ -102,17 +96,13 @@ const commandsModule = ({ PatientWeight: instance.PatientWeight, RadiopharmaceuticalInformationSequence: { RadionuclideTotalDose: - instance.RadiopharmaceuticalInformationSequence[0] - .RadionuclideTotalDose, + instance.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose, RadionuclideHalfLife: - instance.RadiopharmaceuticalInformationSequence[0] - .RadionuclideHalfLife, + instance.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife, RadiopharmaceuticalStartTime: - instance.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartTime, + instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime, RadiopharmaceuticalStartDateTime: - instance.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartDateTime, + instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime, }, }; @@ -137,7 +127,6 @@ const commandsModule = ({ // Add Segmentation to all toolGroupIds in the viewer const toolGroupIds = _getMatchedViewportsToolGroupIds(); - const representationType = LABELMAP; for (const toolGroupId of toolGroupIds) { @@ -149,10 +138,7 @@ const commandsModule = ({ representationType ); - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); } return segmentationId; @@ -161,21 +147,14 @@ const commandsModule = ({ const toolGroupIds = _getMatchedViewportsToolGroupIds(); toolGroupIds.forEach(toolGroupId => { - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); }); }, thresholdSegmentationByRectangleROITool: ({ segmentationId, config }) => { - const segmentation = csTools.segmentation.state.getSegmentation( - segmentationId - ); + const segmentation = csTools.segmentation.state.getSegmentation(segmentationId); const { representationData } = segmentation; - const { - displaySetMatchDetails: matchDetails, - } = hangingProtocolService.getMatchDetails(); + const { displaySetMatchDetails: matchDetails } = hangingProtocolService.getMatchDetails(); const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use const ctDisplaySet = matchDetails.get('ctDisplaySet'); @@ -249,9 +228,7 @@ const commandsModule = ({ getLesionStats: ({ labelmap, segmentIndex = 1 }) => { const { scalarData, spacing } = labelmap; - const { scalarData: referencedScalarData } = cs.cache.getVolume( - labelmap.referencedVolumeId - ); + const { scalarData: referencedScalarData } = cs.cache.getVolume(labelmap.referencedVolumeId); let segmentationMax = -Infinity; let segmentationMin = Infinity; @@ -293,9 +270,7 @@ const commandsModule = ({ }; }, calculateTMTV: ({ segmentations }) => { - const labelmaps = segmentations.map(s => - segmentationService.getLabelmapVolume(s.id) - ); + const labelmaps = segmentations.map(s => segmentationService.getLabelmapVolume(s.id)); if (!labelmaps.length) { return; @@ -318,17 +293,14 @@ const commandsModule = ({ createAndDownloadTMTVReport(segReport, additionalReportRows); }, getTotalLesionGlycolysis: ({ segmentations }) => { - const labelmapVolumes = segmentations.map(s => - segmentationService.getLabelmapVolume(s.id) - ); + const labelmapVolumes = segmentations.map(s => segmentationService.getLabelmapVolume(s.id)); let mergedLabelmap; // merge labelmap will through an error if labels maps are not the same size // or same direction or .... try { - mergedLabelmap = csTools.utilities.segmentation.createMergedLabelmapForIndex( - labelmapVolumes - ); + mergedLabelmap = + csTools.utilities.segmentation.createMergedLabelmapForIndex(labelmapVolumes); } catch (e) { console.error('commandsModule::getTotalLesionGlycolysis', e); return; @@ -338,9 +310,7 @@ const commandsModule = ({ const { referencedVolumeId, spacing } = labelmapVolumes[0]; if (!referencedVolumeId) { - console.error( - 'commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found' - ); + console.error('commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found'); } const ptVolume = cs.cache.getVolume(referencedVolumeId); @@ -366,14 +336,7 @@ const commandsModule = ({ const averageSuv = suv / totalLesionVoxelCount; // total Lesion Glycolysis [suv * ml] - return ( - averageSuv * - totalLesionVoxelCount * - spacing[0] * - spacing[1] * - spacing[2] * - 1e-3 - ); + return averageSuv * totalLesionVoxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3; }, setStartSliceForROIThresholdTool: () => { const { viewport } = _getActiveViewportsEnabledElement(); @@ -489,9 +452,7 @@ const commandsModule = ({ const referencedVolumeId = labelmapVolume.referencedVolumeId; segReport.referencedVolumeId = referencedVolumeId; - const referencedVolume = segmentationService.getLabelmapVolume( - referencedVolumeId - ); + const referencedVolume = segmentationService.getLabelmapVolume(referencedVolumeId); if (!referencedVolume) { report[id] = segReport; @@ -504,10 +465,7 @@ const commandsModule = ({ } const firstImageId = referencedVolume.imageIds[0]; - const instance = OHIF.classes.MetadataProvider.get( - 'instance', - firstImageId - ); + const instance = OHIF.classes.MetadataProvider.get('instance', firstImageId); if (!instance) { report[id] = segReport; @@ -545,20 +503,22 @@ const commandsModule = ({ let viewports = []; fusionViewportIds.forEach(viewportId => { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - - const viewportIndex = viewportInfo.getViewportIndex(); commandsManager.runCommand('setViewportColormap', { - viewportIndex, + viewportId, displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, - colormap, + colormap: { + name: colormap, + // TODO: This opacity mapping matches that in hpViewports, but + // ideally making this editable in a side panel would be useful + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], + }, }); - viewports.push( - cornerstoneViewportService.getCornerstoneViewport(viewportId) - ); + viewports.push(cornerstoneViewportService.getCornerstoneViewport(viewportId)); }); viewports.forEach(viewport => { diff --git a/extensions/tmtv/src/getHangingProtocolModule.js b/extensions/tmtv/src/getHangingProtocolModule.js index 45720c10d52..e123a791766 100644 --- a/extensions/tmtv/src/getHangingProtocolModule.js +++ b/extensions/tmtv/src/getHangingProtocolModule.js @@ -217,7 +217,6 @@ const stage4 = { const ptCT = { id: '@ohif/extension-tmtv.hangingProtocolModule.ptCT', locked: true, - hasUpdatedPriorsInformation: false, name: 'Default', createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2022-10-04T19:22:08.894Z', diff --git a/extensions/tmtv/src/getPanelModule.tsx b/extensions/tmtv/src/getPanelModule.tsx index 94e221fc93e..0449ebe48bf 100644 --- a/extensions/tmtv/src/getPanelModule.tsx +++ b/extensions/tmtv/src/getPanelModule.tsx @@ -6,11 +6,7 @@ import { PanelPetSUV, PanelROIThresholdSegmentation } from './Panels'; // - cancel promises when component is destroyed // - show errors in UI for thumbnails if promise fails -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { +function getPanelModule({ commandsManager, extensionManager, servicesManager }) { const wrappedPanelPetSuv = () => { return ( true, - callback, - boundsIJK - ); + utilities.pointInShapeCallback(labelmapImageData, () => true, callback, boundsIJK); - const direction = labelmapImageData - .getDirection() - .slice(0, 3) as Types.Point3; + const direction = labelmapImageData.getDirection().slice(0, 3) as Types.Point3; /** * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere) @@ -117,13 +98,10 @@ function calculateSuvPeak( const secondaryCircleWorld = vec3.create(); const bottomWorld = vec3.create(); const topWorld = vec3.create(); - referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld); + referenceVolumeImageData.indexToWorld(maxIJK as vec3, secondaryCircleWorld); vec3.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2); vec3.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2); - const suvPeakCirclePoints = [bottomWorld, topWorld] as [ - Types.Point3, - Types.Point3 - ]; + const suvPeakCirclePoints = [bottomWorld, topWorld] as [Types.Point3, Types.Point3]; /** * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous diff --git a/extensions/tmtv/src/utils/calculateTMTV.ts b/extensions/tmtv/src/utils/calculateTMTV.ts index f09375c346e..8d9f5f3e5d9 100644 --- a/extensions/tmtv/src/utils/calculateTMTV.ts +++ b/extensions/tmtv/src/utils/calculateTMTV.ts @@ -11,10 +11,7 @@ import { utilities } from '@cornerstonejs/tools'; * @param {number} segmentIndex * @returns {number} TMTV in ml */ -function calculateTMTV( - labelmaps: Array, - segmentIndex = 1 -): number { +function calculateTMTV(labelmaps: Array, segmentIndex = 1): number { const volumeId = 'mergedLabelmap'; const mergedLabelmap = utilities.segmentation.createMergedLabelmapForIndex( @@ -24,10 +21,7 @@ function calculateTMTV( ); const { imageData, spacing } = mergedLabelmap; - const values = imageData - .getPointData() - .getScalars() - .getData(); + const values = imageData.getPointData().getScalars().getData(); // count non-zero values inside the outputData, this would // consider the overlapping regions to be only counted once diff --git a/extensions/tmtv/src/utils/colormaps/index.js b/extensions/tmtv/src/utils/colormaps/index.js index 34761d25963..b5afdd984a2 100644 --- a/extensions/tmtv/src/utils/colormaps/index.js +++ b/extensions/tmtv/src/utils/colormaps/index.js @@ -3,9270 +3,1552 @@ export default [ ColorSpace: 'RGB', Name: 'hot_iron', RGBPoints: [ - 0.0, - 0.0039215686, - 0.0039215686, - 0.0156862745, - 0.00392156862745098, - 0.0039215686, - 0.0039215686, - 0.0156862745, - 0.00784313725490196, - 0.0039215686, - 0.0039215686, - 0.031372549, - 0.011764705882352941, - 0.0039215686, - 0.0039215686, - 0.0470588235, - 0.01568627450980392, - 0.0039215686, - 0.0039215686, - 0.062745098, - 0.0196078431372549, - 0.0039215686, - 0.0039215686, - 0.0784313725, - 0.023529411764705882, - 0.0039215686, - 0.0039215686, - 0.0941176471, - 0.027450980392156862, - 0.0039215686, - 0.0039215686, - 0.1098039216, - 0.03137254901960784, - 0.0039215686, - 0.0039215686, - 0.1254901961, - 0.03529411764705882, - 0.0039215686, - 0.0039215686, - 0.1411764706, - 0.0392156862745098, - 0.0039215686, - 0.0039215686, - 0.1568627451, - 0.043137254901960784, - 0.0039215686, - 0.0039215686, - 0.1725490196, - 0.047058823529411764, - 0.0039215686, - 0.0039215686, - 0.1882352941, - 0.050980392156862744, - 0.0039215686, - 0.0039215686, - 0.2039215686, - 0.054901960784313725, - 0.0039215686, - 0.0039215686, - 0.2196078431, - 0.05882352941176471, - 0.0039215686, - 0.0039215686, - 0.2352941176, - 0.06274509803921569, - 0.0039215686, - 0.0039215686, - 0.2509803922, - 0.06666666666666667, - 0.0039215686, - 0.0039215686, - 0.262745098, - 0.07058823529411765, - 0.0039215686, - 0.0039215686, - 0.2784313725, - 0.07450980392156863, - 0.0039215686, - 0.0039215686, - 0.2941176471, - 0.0784313725490196, - 0.0039215686, - 0.0039215686, - 0.3098039216, - 0.08235294117647059, - 0.0039215686, - 0.0039215686, - 0.3254901961, - 0.08627450980392157, - 0.0039215686, - 0.0039215686, - 0.3411764706, - 0.09019607843137255, - 0.0039215686, - 0.0039215686, - 0.3568627451, - 0.09411764705882353, - 0.0039215686, - 0.0039215686, - 0.3725490196, - 0.09803921568627451, - 0.0039215686, - 0.0039215686, - 0.3882352941, - 0.10196078431372549, - 0.0039215686, - 0.0039215686, - 0.4039215686, - 0.10588235294117647, - 0.0039215686, - 0.0039215686, - 0.4196078431, - 0.10980392156862745, - 0.0039215686, - 0.0039215686, - 0.4352941176, - 0.11372549019607843, - 0.0039215686, - 0.0039215686, - 0.4509803922, - 0.11764705882352942, - 0.0039215686, - 0.0039215686, - 0.4666666667, - 0.12156862745098039, - 0.0039215686, - 0.0039215686, - 0.4823529412, - 0.12549019607843137, - 0.0039215686, - 0.0039215686, - 0.4980392157, - 0.12941176470588237, - 0.0039215686, - 0.0039215686, - 0.5137254902, - 0.13333333333333333, - 0.0039215686, - 0.0039215686, - 0.5294117647, - 0.13725490196078433, - 0.0039215686, - 0.0039215686, - 0.5450980392, - 0.1411764705882353, - 0.0039215686, - 0.0039215686, - 0.5607843137, - 0.1450980392156863, - 0.0039215686, - 0.0039215686, - 0.5764705882, - 0.14901960784313725, - 0.0039215686, - 0.0039215686, - 0.5921568627, - 0.15294117647058825, - 0.0039215686, - 0.0039215686, - 0.6078431373, - 0.1568627450980392, - 0.0039215686, - 0.0039215686, - 0.6235294118, - 0.1607843137254902, - 0.0039215686, - 0.0039215686, - 0.6392156863, - 0.16470588235294117, - 0.0039215686, - 0.0039215686, - 0.6549019608, - 0.16862745098039217, - 0.0039215686, - 0.0039215686, - 0.6705882353, - 0.17254901960784313, - 0.0039215686, - 0.0039215686, - 0.6862745098, - 0.17647058823529413, - 0.0039215686, - 0.0039215686, - 0.7019607843, - 0.1803921568627451, - 0.0039215686, - 0.0039215686, - 0.7176470588, - 0.1843137254901961, - 0.0039215686, - 0.0039215686, - 0.7333333333, - 0.18823529411764706, - 0.0039215686, - 0.0039215686, - 0.7490196078, - 0.19215686274509805, - 0.0039215686, - 0.0039215686, - 0.7607843137, - 0.19607843137254902, - 0.0039215686, - 0.0039215686, - 0.7764705882, - 0.2, - 0.0039215686, - 0.0039215686, - 0.7921568627, - 0.20392156862745098, - 0.0039215686, - 0.0039215686, - 0.8078431373, - 0.20784313725490197, - 0.0039215686, - 0.0039215686, - 0.8235294118, - 0.21176470588235294, - 0.0039215686, - 0.0039215686, - 0.8392156863, - 0.21568627450980393, - 0.0039215686, - 0.0039215686, - 0.8549019608, - 0.2196078431372549, - 0.0039215686, - 0.0039215686, - 0.8705882353, - 0.2235294117647059, - 0.0039215686, - 0.0039215686, - 0.8862745098, - 0.22745098039215686, - 0.0039215686, - 0.0039215686, - 0.9019607843, - 0.23137254901960785, - 0.0039215686, - 0.0039215686, - 0.9176470588, - 0.23529411764705885, - 0.0039215686, - 0.0039215686, - 0.9333333333, - 0.23921568627450984, - 0.0039215686, - 0.0039215686, - 0.9490196078, - 0.24313725490196078, - 0.0039215686, - 0.0039215686, - 0.9647058824, - 0.24705882352941178, - 0.0039215686, - 0.0039215686, - 0.9803921569, - 0.25098039215686274, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.2549019607843137, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.25882352941176473, - 0.0156862745, - 0.0039215686, - 0.9803921569, - 0.2627450980392157, - 0.031372549, - 0.0039215686, - 0.9647058824, - 0.26666666666666666, - 0.0470588235, - 0.0039215686, - 0.9490196078, - 0.27058823529411763, - 0.062745098, - 0.0039215686, - 0.9333333333, - 0.27450980392156865, - 0.0784313725, - 0.0039215686, - 0.9176470588, - 0.2784313725490196, - 0.0941176471, - 0.0039215686, - 0.9019607843, - 0.2823529411764706, - 0.1098039216, - 0.0039215686, - 0.8862745098, - 0.28627450980392155, - 0.1254901961, - 0.0039215686, - 0.8705882353, - 0.2901960784313726, - 0.1411764706, - 0.0039215686, - 0.8549019608, - 0.29411764705882354, - 0.1568627451, - 0.0039215686, - 0.8392156863, - 0.2980392156862745, - 0.1725490196, - 0.0039215686, - 0.8235294118, - 0.30196078431372547, - 0.1882352941, - 0.0039215686, - 0.8078431373, - 0.3058823529411765, - 0.2039215686, - 0.0039215686, - 0.7921568627, - 0.30980392156862746, - 0.2196078431, - 0.0039215686, - 0.7764705882, - 0.3137254901960784, - 0.2352941176, - 0.0039215686, - 0.7607843137, - 0.3176470588235294, - 0.2509803922, - 0.0039215686, - 0.7490196078, - 0.3215686274509804, - 0.262745098, - 0.0039215686, - 0.7333333333, - 0.3254901960784314, - 0.2784313725, - 0.0039215686, - 0.7176470588, - 0.32941176470588235, - 0.2941176471, - 0.0039215686, - 0.7019607843, - 0.3333333333333333, - 0.3098039216, - 0.0039215686, - 0.6862745098, - 0.33725490196078434, - 0.3254901961, - 0.0039215686, - 0.6705882353, - 0.3411764705882353, - 0.3411764706, - 0.0039215686, - 0.6549019608, - 0.34509803921568627, - 0.3568627451, - 0.0039215686, - 0.6392156863, - 0.34901960784313724, - 0.3725490196, - 0.0039215686, - 0.6235294118, - 0.35294117647058826, - 0.3882352941, - 0.0039215686, - 0.6078431373, - 0.3568627450980392, - 0.4039215686, - 0.0039215686, - 0.5921568627, - 0.3607843137254902, - 0.4196078431, - 0.0039215686, - 0.5764705882, - 0.36470588235294116, - 0.4352941176, - 0.0039215686, - 0.5607843137, - 0.3686274509803922, - 0.4509803922, - 0.0039215686, - 0.5450980392, - 0.37254901960784315, - 0.4666666667, - 0.0039215686, - 0.5294117647, - 0.3764705882352941, - 0.4823529412, - 0.0039215686, - 0.5137254902, - 0.3803921568627451, - 0.4980392157, - 0.0039215686, - 0.4980392157, - 0.3843137254901961, - 0.5137254902, - 0.0039215686, - 0.4823529412, - 0.38823529411764707, - 0.5294117647, - 0.0039215686, - 0.4666666667, - 0.39215686274509803, - 0.5450980392, - 0.0039215686, - 0.4509803922, - 0.396078431372549, - 0.5607843137, - 0.0039215686, - 0.4352941176, - 0.4, - 0.5764705882, - 0.0039215686, - 0.4196078431, - 0.403921568627451, - 0.5921568627, - 0.0039215686, - 0.4039215686, - 0.40784313725490196, - 0.6078431373, - 0.0039215686, - 0.3882352941, - 0.4117647058823529, - 0.6235294118, - 0.0039215686, - 0.3725490196, - 0.41568627450980394, - 0.6392156863, - 0.0039215686, - 0.3568627451, - 0.4196078431372549, - 0.6549019608, - 0.0039215686, - 0.3411764706, - 0.4235294117647059, - 0.6705882353, - 0.0039215686, - 0.3254901961, - 0.42745098039215684, - 0.6862745098, - 0.0039215686, - 0.3098039216, - 0.43137254901960786, - 0.7019607843, - 0.0039215686, - 0.2941176471, - 0.43529411764705883, - 0.7176470588, - 0.0039215686, - 0.2784313725, - 0.4392156862745098, - 0.7333333333, - 0.0039215686, - 0.262745098, - 0.44313725490196076, - 0.7490196078, - 0.0039215686, - 0.2509803922, - 0.4470588235294118, - 0.7607843137, - 0.0039215686, - 0.2352941176, - 0.45098039215686275, - 0.7764705882, - 0.0039215686, - 0.2196078431, - 0.4549019607843137, - 0.7921568627, - 0.0039215686, - 0.2039215686, - 0.4588235294117647, - 0.8078431373, - 0.0039215686, - 0.1882352941, - 0.4627450980392157, - 0.8235294118, - 0.0039215686, - 0.1725490196, - 0.4666666666666667, - 0.8392156863, - 0.0039215686, - 0.1568627451, - 0.4705882352941177, - 0.8549019608, - 0.0039215686, - 0.1411764706, - 0.4745098039215686, - 0.8705882353, - 0.0039215686, - 0.1254901961, - 0.4784313725490197, - 0.8862745098, - 0.0039215686, - 0.1098039216, - 0.48235294117647065, - 0.9019607843, - 0.0039215686, - 0.0941176471, - 0.48627450980392156, - 0.9176470588, - 0.0039215686, - 0.0784313725, - 0.49019607843137253, - 0.9333333333, - 0.0039215686, - 0.062745098, - 0.49411764705882355, - 0.9490196078, - 0.0039215686, - 0.0470588235, - 0.4980392156862745, - 0.9647058824, - 0.0039215686, - 0.031372549, - 0.5019607843137255, - 0.9803921569, - 0.0039215686, - 0.0156862745, - 0.5058823529411764, - 0.9960784314, - 0.0039215686, - 0.0039215686, - 0.5098039215686274, - 0.9960784314, - 0.0156862745, - 0.0039215686, - 0.5137254901960784, - 0.9960784314, - 0.031372549, - 0.0039215686, - 0.5176470588235295, - 0.9960784314, - 0.0470588235, - 0.0039215686, - 0.5215686274509804, - 0.9960784314, - 0.062745098, - 0.0039215686, - 0.5254901960784314, - 0.9960784314, - 0.0784313725, - 0.0039215686, - 0.5294117647058824, - 0.9960784314, - 0.0941176471, - 0.0039215686, - 0.5333333333333333, - 0.9960784314, - 0.1098039216, - 0.0039215686, - 0.5372549019607843, - 0.9960784314, - 0.1254901961, - 0.0039215686, - 0.5411764705882353, - 0.9960784314, - 0.1411764706, - 0.0039215686, - 0.5450980392156862, - 0.9960784314, - 0.1568627451, - 0.0039215686, - 0.5490196078431373, - 0.9960784314, - 0.1725490196, - 0.0039215686, - 0.5529411764705883, - 0.9960784314, - 0.1882352941, - 0.0039215686, - 0.5568627450980392, - 0.9960784314, - 0.2039215686, - 0.0039215686, - 0.5607843137254902, - 0.9960784314, - 0.2196078431, - 0.0039215686, - 0.5647058823529412, - 0.9960784314, - 0.2352941176, - 0.0039215686, - 0.5686274509803921, - 0.9960784314, - 0.2509803922, - 0.0039215686, - 0.5725490196078431, - 0.9960784314, - 0.262745098, - 0.0039215686, - 0.5764705882352941, - 0.9960784314, - 0.2784313725, - 0.0039215686, - 0.5803921568627451, - 0.9960784314, - 0.2941176471, - 0.0039215686, - 0.5843137254901961, - 0.9960784314, - 0.3098039216, - 0.0039215686, - 0.5882352941176471, - 0.9960784314, - 0.3254901961, - 0.0039215686, - 0.592156862745098, - 0.9960784314, - 0.3411764706, - 0.0039215686, - 0.596078431372549, - 0.9960784314, - 0.3568627451, - 0.0039215686, - 0.6, - 0.9960784314, - 0.3725490196, - 0.0039215686, - 0.6039215686274509, - 0.9960784314, - 0.3882352941, - 0.0039215686, - 0.6078431372549019, - 0.9960784314, - 0.4039215686, - 0.0039215686, - 0.611764705882353, - 0.9960784314, - 0.4196078431, - 0.0039215686, - 0.615686274509804, - 0.9960784314, - 0.4352941176, - 0.0039215686, - 0.6196078431372549, - 0.9960784314, - 0.4509803922, - 0.0039215686, - 0.6235294117647059, - 0.9960784314, - 0.4666666667, - 0.0039215686, - 0.6274509803921569, - 0.9960784314, - 0.4823529412, - 0.0039215686, - 0.6313725490196078, - 0.9960784314, - 0.4980392157, - 0.0039215686, - 0.6352941176470588, - 0.9960784314, - 0.5137254902, - 0.0039215686, - 0.6392156862745098, - 0.9960784314, - 0.5294117647, - 0.0039215686, - 0.6431372549019608, - 0.9960784314, - 0.5450980392, - 0.0039215686, - 0.6470588235294118, - 0.9960784314, - 0.5607843137, - 0.0039215686, - 0.6509803921568628, - 0.9960784314, - 0.5764705882, - 0.0039215686, - 0.6549019607843137, - 0.9960784314, - 0.5921568627, - 0.0039215686, - 0.6588235294117647, - 0.9960784314, - 0.6078431373, - 0.0039215686, - 0.6627450980392157, - 0.9960784314, - 0.6235294118, - 0.0039215686, - 0.6666666666666666, - 0.9960784314, - 0.6392156863, - 0.0039215686, - 0.6705882352941176, - 0.9960784314, - 0.6549019608, - 0.0039215686, - 0.6745098039215687, - 0.9960784314, - 0.6705882353, - 0.0039215686, - 0.6784313725490196, - 0.9960784314, - 0.6862745098, - 0.0039215686, - 0.6823529411764706, - 0.9960784314, - 0.7019607843, - 0.0039215686, - 0.6862745098039216, - 0.9960784314, - 0.7176470588, - 0.0039215686, - 0.6901960784313725, - 0.9960784314, - 0.7333333333, - 0.0039215686, - 0.6941176470588235, - 0.9960784314, - 0.7490196078, - 0.0039215686, - 0.6980392156862745, - 0.9960784314, - 0.7607843137, - 0.0039215686, - 0.7019607843137254, - 0.9960784314, - 0.7764705882, - 0.0039215686, - 0.7058823529411765, - 0.9960784314, - 0.7921568627, - 0.0039215686, - 0.7098039215686275, - 0.9960784314, - 0.8078431373, - 0.0039215686, - 0.7137254901960784, - 0.9960784314, - 0.8235294118, - 0.0039215686, - 0.7176470588235294, - 0.9960784314, - 0.8392156863, - 0.0039215686, - 0.7215686274509804, - 0.9960784314, - 0.8549019608, - 0.0039215686, - 0.7254901960784313, - 0.9960784314, - 0.8705882353, - 0.0039215686, - 0.7294117647058823, - 0.9960784314, - 0.8862745098, - 0.0039215686, - 0.7333333333333333, - 0.9960784314, - 0.9019607843, - 0.0039215686, - 0.7372549019607844, - 0.9960784314, - 0.9176470588, - 0.0039215686, - 0.7411764705882353, - 0.9960784314, - 0.9333333333, - 0.0039215686, - 0.7450980392156863, - 0.9960784314, - 0.9490196078, - 0.0039215686, - 0.7490196078431373, - 0.9960784314, - 0.9647058824, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.9803921569, - 0.0039215686, - 0.7568627450980392, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.7607843137254902, - 0.9960784314, - 0.9960784314, - 0.0196078431, - 0.7647058823529411, - 0.9960784314, - 0.9960784314, - 0.0352941176, - 0.7686274509803922, - 0.9960784314, - 0.9960784314, - 0.0509803922, - 0.7725490196078432, - 0.9960784314, - 0.9960784314, - 0.0666666667, - 0.7764705882352941, - 0.9960784314, - 0.9960784314, - 0.0823529412, - 0.7803921568627451, - 0.9960784314, - 0.9960784314, - 0.0980392157, - 0.7843137254901961, - 0.9960784314, - 0.9960784314, - 0.1137254902, - 0.788235294117647, - 0.9960784314, - 0.9960784314, - 0.1294117647, - 0.792156862745098, - 0.9960784314, - 0.9960784314, - 0.1450980392, - 0.796078431372549, - 0.9960784314, - 0.9960784314, - 0.1607843137, - 0.8, - 0.9960784314, - 0.9960784314, - 0.1764705882, - 0.803921568627451, - 0.9960784314, - 0.9960784314, - 0.1921568627, - 0.807843137254902, - 0.9960784314, - 0.9960784314, - 0.2078431373, - 0.8117647058823529, - 0.9960784314, - 0.9960784314, - 0.2235294118, - 0.8156862745098039, - 0.9960784314, - 0.9960784314, - 0.2392156863, - 0.8196078431372549, - 0.9960784314, - 0.9960784314, - 0.2509803922, - 0.8235294117647058, - 0.9960784314, - 0.9960784314, - 0.2666666667, - 0.8274509803921568, - 0.9960784314, - 0.9960784314, - 0.2823529412, - 0.8313725490196079, - 0.9960784314, - 0.9960784314, - 0.2980392157, - 0.8352941176470589, - 0.9960784314, - 0.9960784314, - 0.3137254902, - 0.8392156862745098, - 0.9960784314, - 0.9960784314, - 0.3333333333, - 0.8431372549019608, - 0.9960784314, - 0.9960784314, - 0.3490196078, - 0.8470588235294118, - 0.9960784314, - 0.9960784314, - 0.3647058824, - 0.8509803921568627, - 0.9960784314, - 0.9960784314, - 0.3803921569, - 0.8549019607843137, - 0.9960784314, - 0.9960784314, - 0.3960784314, - 0.8588235294117647, - 0.9960784314, - 0.9960784314, - 0.4117647059, - 0.8627450980392157, - 0.9960784314, - 0.9960784314, - 0.4274509804, - 0.8666666666666667, - 0.9960784314, - 0.9960784314, - 0.4431372549, - 0.8705882352941177, - 0.9960784314, - 0.9960784314, - 0.4588235294, - 0.8745098039215686, - 0.9960784314, - 0.9960784314, - 0.4745098039, - 0.8784313725490196, - 0.9960784314, - 0.9960784314, - 0.4901960784, - 0.8823529411764706, - 0.9960784314, - 0.9960784314, - 0.5058823529, - 0.8862745098039215, - 0.9960784314, - 0.9960784314, - 0.5215686275, - 0.8901960784313725, - 0.9960784314, - 0.9960784314, - 0.537254902, - 0.8941176470588236, - 0.9960784314, - 0.9960784314, - 0.5529411765, - 0.8980392156862745, - 0.9960784314, - 0.9960784314, - 0.568627451, - 0.9019607843137255, - 0.9960784314, - 0.9960784314, - 0.5843137255, - 0.9058823529411765, - 0.9960784314, - 0.9960784314, - 0.6, - 0.9098039215686274, - 0.9960784314, - 0.9960784314, - 0.6156862745, - 0.9137254901960784, - 0.9960784314, - 0.9960784314, - 0.631372549, - 0.9176470588235294, - 0.9960784314, - 0.9960784314, - 0.6470588235, - 0.9215686274509803, - 0.9960784314, - 0.9960784314, - 0.6666666667, - 0.9254901960784314, - 0.9960784314, - 0.9960784314, - 0.6823529412, - 0.9294117647058824, - 0.9960784314, - 0.9960784314, - 0.6980392157, - 0.9333333333333333, - 0.9960784314, - 0.9960784314, - 0.7137254902, - 0.9372549019607843, - 0.9960784314, - 0.9960784314, - 0.7294117647, - 0.9411764705882354, - 0.9960784314, - 0.9960784314, - 0.7450980392, - 0.9450980392156864, - 0.9960784314, - 0.9960784314, - 0.7568627451, - 0.9490196078431372, - 0.9960784314, - 0.9960784314, - 0.7725490196, - 0.9529411764705882, - 0.9960784314, - 0.9960784314, - 0.7882352941, - 0.9568627450980394, - 0.9960784314, - 0.9960784314, - 0.8039215686, - 0.9607843137254903, - 0.9960784314, - 0.9960784314, - 0.8196078431, - 0.9647058823529413, - 0.9960784314, - 0.9960784314, - 0.8352941176, - 0.9686274509803922, - 0.9960784314, - 0.9960784314, - 0.8509803922, - 0.9725490196078431, - 0.9960784314, - 0.9960784314, - 0.8666666667, - 0.9764705882352941, - 0.9960784314, - 0.9960784314, - 0.8823529412, - 0.9803921568627451, - 0.9960784314, - 0.9960784314, - 0.8980392157, - 0.984313725490196, - 0.9960784314, - 0.9960784314, - 0.9137254902, - 0.9882352941176471, - 0.9960784314, - 0.9960784314, - 0.9294117647, - 0.9921568627450981, - 0.9960784314, - 0.9960784314, - 0.9450980392, - 0.996078431372549, - 0.9960784314, - 0.9960784314, - 0.9607843137, - 1.0, - 0.9960784314, - 0.9960784314, - 0.9607843137, + 0.0, 0.0039215686, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0039215686, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0039215686, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0039215686, 0.0039215686, 0.0470588235, 0.01568627450980392, + 0.0039215686, 0.0039215686, 0.062745098, 0.0196078431372549, 0.0039215686, 0.0039215686, + 0.0784313725, 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0941176471, + 0.027450980392156862, 0.0039215686, 0.0039215686, 0.1098039216, 0.03137254901960784, + 0.0039215686, 0.0039215686, 0.1254901961, 0.03529411764705882, 0.0039215686, 0.0039215686, + 0.1411764706, 0.0392156862745098, 0.0039215686, 0.0039215686, 0.1568627451, + 0.043137254901960784, 0.0039215686, 0.0039215686, 0.1725490196, 0.047058823529411764, + 0.0039215686, 0.0039215686, 0.1882352941, 0.050980392156862744, 0.0039215686, 0.0039215686, + 0.2039215686, 0.054901960784313725, 0.0039215686, 0.0039215686, 0.2196078431, + 0.05882352941176471, 0.0039215686, 0.0039215686, 0.2352941176, 0.06274509803921569, + 0.0039215686, 0.0039215686, 0.2509803922, 0.06666666666666667, 0.0039215686, 0.0039215686, + 0.262745098, 0.07058823529411765, 0.0039215686, 0.0039215686, 0.2784313725, + 0.07450980392156863, 0.0039215686, 0.0039215686, 0.2941176471, 0.0784313725490196, + 0.0039215686, 0.0039215686, 0.3098039216, 0.08235294117647059, 0.0039215686, 0.0039215686, + 0.3254901961, 0.08627450980392157, 0.0039215686, 0.0039215686, 0.3411764706, + 0.09019607843137255, 0.0039215686, 0.0039215686, 0.3568627451, 0.09411764705882353, + 0.0039215686, 0.0039215686, 0.3725490196, 0.09803921568627451, 0.0039215686, 0.0039215686, + 0.3882352941, 0.10196078431372549, 0.0039215686, 0.0039215686, 0.4039215686, + 0.10588235294117647, 0.0039215686, 0.0039215686, 0.4196078431, 0.10980392156862745, + 0.0039215686, 0.0039215686, 0.4352941176, 0.11372549019607843, 0.0039215686, 0.0039215686, + 0.4509803922, 0.11764705882352942, 0.0039215686, 0.0039215686, 0.4666666667, + 0.12156862745098039, 0.0039215686, 0.0039215686, 0.4823529412, 0.12549019607843137, + 0.0039215686, 0.0039215686, 0.4980392157, 0.12941176470588237, 0.0039215686, 0.0039215686, + 0.5137254902, 0.13333333333333333, 0.0039215686, 0.0039215686, 0.5294117647, + 0.13725490196078433, 0.0039215686, 0.0039215686, 0.5450980392, 0.1411764705882353, + 0.0039215686, 0.0039215686, 0.5607843137, 0.1450980392156863, 0.0039215686, 0.0039215686, + 0.5764705882, 0.14901960784313725, 0.0039215686, 0.0039215686, 0.5921568627, + 0.15294117647058825, 0.0039215686, 0.0039215686, 0.6078431373, 0.1568627450980392, + 0.0039215686, 0.0039215686, 0.6235294118, 0.1607843137254902, 0.0039215686, 0.0039215686, + 0.6392156863, 0.16470588235294117, 0.0039215686, 0.0039215686, 0.6549019608, + 0.16862745098039217, 0.0039215686, 0.0039215686, 0.6705882353, 0.17254901960784313, + 0.0039215686, 0.0039215686, 0.6862745098, 0.17647058823529413, 0.0039215686, 0.0039215686, + 0.7019607843, 0.1803921568627451, 0.0039215686, 0.0039215686, 0.7176470588, + 0.1843137254901961, 0.0039215686, 0.0039215686, 0.7333333333, 0.18823529411764706, + 0.0039215686, 0.0039215686, 0.7490196078, 0.19215686274509805, 0.0039215686, 0.0039215686, + 0.7607843137, 0.19607843137254902, 0.0039215686, 0.0039215686, 0.7764705882, 0.2, + 0.0039215686, 0.0039215686, 0.7921568627, 0.20392156862745098, 0.0039215686, 0.0039215686, + 0.8078431373, 0.20784313725490197, 0.0039215686, 0.0039215686, 0.8235294118, + 0.21176470588235294, 0.0039215686, 0.0039215686, 0.8392156863, 0.21568627450980393, + 0.0039215686, 0.0039215686, 0.8549019608, 0.2196078431372549, 0.0039215686, 0.0039215686, + 0.8705882353, 0.2235294117647059, 0.0039215686, 0.0039215686, 0.8862745098, + 0.22745098039215686, 0.0039215686, 0.0039215686, 0.9019607843, 0.23137254901960785, + 0.0039215686, 0.0039215686, 0.9176470588, 0.23529411764705885, 0.0039215686, 0.0039215686, + 0.9333333333, 0.23921568627450984, 0.0039215686, 0.0039215686, 0.9490196078, + 0.24313725490196078, 0.0039215686, 0.0039215686, 0.9647058824, 0.24705882352941178, + 0.0039215686, 0.0039215686, 0.9803921569, 0.25098039215686274, 0.0039215686, 0.0039215686, + 0.9960784314, 0.2549019607843137, 0.0039215686, 0.0039215686, 0.9960784314, + 0.25882352941176473, 0.0156862745, 0.0039215686, 0.9803921569, 0.2627450980392157, + 0.031372549, 0.0039215686, 0.9647058824, 0.26666666666666666, 0.0470588235, 0.0039215686, + 0.9490196078, 0.27058823529411763, 0.062745098, 0.0039215686, 0.9333333333, + 0.27450980392156865, 0.0784313725, 0.0039215686, 0.9176470588, 0.2784313725490196, + 0.0941176471, 0.0039215686, 0.9019607843, 0.2823529411764706, 0.1098039216, 0.0039215686, + 0.8862745098, 0.28627450980392155, 0.1254901961, 0.0039215686, 0.8705882353, + 0.2901960784313726, 0.1411764706, 0.0039215686, 0.8549019608, 0.29411764705882354, + 0.1568627451, 0.0039215686, 0.8392156863, 0.2980392156862745, 0.1725490196, 0.0039215686, + 0.8235294118, 0.30196078431372547, 0.1882352941, 0.0039215686, 0.8078431373, + 0.3058823529411765, 0.2039215686, 0.0039215686, 0.7921568627, 0.30980392156862746, + 0.2196078431, 0.0039215686, 0.7764705882, 0.3137254901960784, 0.2352941176, 0.0039215686, + 0.7607843137, 0.3176470588235294, 0.2509803922, 0.0039215686, 0.7490196078, + 0.3215686274509804, 0.262745098, 0.0039215686, 0.7333333333, 0.3254901960784314, 0.2784313725, + 0.0039215686, 0.7176470588, 0.32941176470588235, 0.2941176471, 0.0039215686, 0.7019607843, + 0.3333333333333333, 0.3098039216, 0.0039215686, 0.6862745098, 0.33725490196078434, + 0.3254901961, 0.0039215686, 0.6705882353, 0.3411764705882353, 0.3411764706, 0.0039215686, + 0.6549019608, 0.34509803921568627, 0.3568627451, 0.0039215686, 0.6392156863, + 0.34901960784313724, 0.3725490196, 0.0039215686, 0.6235294118, 0.35294117647058826, + 0.3882352941, 0.0039215686, 0.6078431373, 0.3568627450980392, 0.4039215686, 0.0039215686, + 0.5921568627, 0.3607843137254902, 0.4196078431, 0.0039215686, 0.5764705882, + 0.36470588235294116, 0.4352941176, 0.0039215686, 0.5607843137, 0.3686274509803922, + 0.4509803922, 0.0039215686, 0.5450980392, 0.37254901960784315, 0.4666666667, 0.0039215686, + 0.5294117647, 0.3764705882352941, 0.4823529412, 0.0039215686, 0.5137254902, + 0.3803921568627451, 0.4980392157, 0.0039215686, 0.4980392157, 0.3843137254901961, + 0.5137254902, 0.0039215686, 0.4823529412, 0.38823529411764707, 0.5294117647, 0.0039215686, + 0.4666666667, 0.39215686274509803, 0.5450980392, 0.0039215686, 0.4509803922, + 0.396078431372549, 0.5607843137, 0.0039215686, 0.4352941176, 0.4, 0.5764705882, 0.0039215686, + 0.4196078431, 0.403921568627451, 0.5921568627, 0.0039215686, 0.4039215686, + 0.40784313725490196, 0.6078431373, 0.0039215686, 0.3882352941, 0.4117647058823529, + 0.6235294118, 0.0039215686, 0.3725490196, 0.41568627450980394, 0.6392156863, 0.0039215686, + 0.3568627451, 0.4196078431372549, 0.6549019608, 0.0039215686, 0.3411764706, + 0.4235294117647059, 0.6705882353, 0.0039215686, 0.3254901961, 0.42745098039215684, + 0.6862745098, 0.0039215686, 0.3098039216, 0.43137254901960786, 0.7019607843, 0.0039215686, + 0.2941176471, 0.43529411764705883, 0.7176470588, 0.0039215686, 0.2784313725, + 0.4392156862745098, 0.7333333333, 0.0039215686, 0.262745098, 0.44313725490196076, + 0.7490196078, 0.0039215686, 0.2509803922, 0.4470588235294118, 0.7607843137, 0.0039215686, + 0.2352941176, 0.45098039215686275, 0.7764705882, 0.0039215686, 0.2196078431, + 0.4549019607843137, 0.7921568627, 0.0039215686, 0.2039215686, 0.4588235294117647, + 0.8078431373, 0.0039215686, 0.1882352941, 0.4627450980392157, 0.8235294118, 0.0039215686, + 0.1725490196, 0.4666666666666667, 0.8392156863, 0.0039215686, 0.1568627451, + 0.4705882352941177, 0.8549019608, 0.0039215686, 0.1411764706, 0.4745098039215686, + 0.8705882353, 0.0039215686, 0.1254901961, 0.4784313725490197, 0.8862745098, 0.0039215686, + 0.1098039216, 0.48235294117647065, 0.9019607843, 0.0039215686, 0.0941176471, + 0.48627450980392156, 0.9176470588, 0.0039215686, 0.0784313725, 0.49019607843137253, + 0.9333333333, 0.0039215686, 0.062745098, 0.49411764705882355, 0.9490196078, 0.0039215686, + 0.0470588235, 0.4980392156862745, 0.9647058824, 0.0039215686, 0.031372549, 0.5019607843137255, + 0.9803921569, 0.0039215686, 0.0156862745, 0.5058823529411764, 0.9960784314, 0.0039215686, + 0.0039215686, 0.5098039215686274, 0.9960784314, 0.0156862745, 0.0039215686, + 0.5137254901960784, 0.9960784314, 0.031372549, 0.0039215686, 0.5176470588235295, 0.9960784314, + 0.0470588235, 0.0039215686, 0.5215686274509804, 0.9960784314, 0.062745098, 0.0039215686, + 0.5254901960784314, 0.9960784314, 0.0784313725, 0.0039215686, 0.5294117647058824, + 0.9960784314, 0.0941176471, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.1098039216, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.1254901961, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.1411764706, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.1568627451, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.1725490196, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.1882352941, 0.0039215686, + 0.5568627450980392, 0.9960784314, 0.2039215686, 0.0039215686, 0.5607843137254902, + 0.9960784314, 0.2196078431, 0.0039215686, 0.5647058823529412, 0.9960784314, 0.2352941176, + 0.0039215686, 0.5686274509803921, 0.9960784314, 0.2509803922, 0.0039215686, + 0.5725490196078431, 0.9960784314, 0.262745098, 0.0039215686, 0.5764705882352941, 0.9960784314, + 0.2784313725, 0.0039215686, 0.5803921568627451, 0.9960784314, 0.2941176471, 0.0039215686, + 0.5843137254901961, 0.9960784314, 0.3098039216, 0.0039215686, 0.5882352941176471, + 0.9960784314, 0.3254901961, 0.0039215686, 0.592156862745098, 0.9960784314, 0.3411764706, + 0.0039215686, 0.596078431372549, 0.9960784314, 0.3568627451, 0.0039215686, 0.6, 0.9960784314, + 0.3725490196, 0.0039215686, 0.6039215686274509, 0.9960784314, 0.3882352941, 0.0039215686, + 0.6078431372549019, 0.9960784314, 0.4039215686, 0.0039215686, 0.611764705882353, 0.9960784314, + 0.4196078431, 0.0039215686, 0.615686274509804, 0.9960784314, 0.4352941176, 0.0039215686, + 0.6196078431372549, 0.9960784314, 0.4509803922, 0.0039215686, 0.6235294117647059, + 0.9960784314, 0.4666666667, 0.0039215686, 0.6274509803921569, 0.9960784314, 0.4823529412, + 0.0039215686, 0.6313725490196078, 0.9960784314, 0.4980392157, 0.0039215686, + 0.6352941176470588, 0.9960784314, 0.5137254902, 0.0039215686, 0.6392156862745098, + 0.9960784314, 0.5294117647, 0.0039215686, 0.6431372549019608, 0.9960784314, 0.5450980392, + 0.0039215686, 0.6470588235294118, 0.9960784314, 0.5607843137, 0.0039215686, + 0.6509803921568628, 0.9960784314, 0.5764705882, 0.0039215686, 0.6549019607843137, + 0.9960784314, 0.5921568627, 0.0039215686, 0.6588235294117647, 0.9960784314, 0.6078431373, + 0.0039215686, 0.6627450980392157, 0.9960784314, 0.6235294118, 0.0039215686, + 0.6666666666666666, 0.9960784314, 0.6392156863, 0.0039215686, 0.6705882352941176, + 0.9960784314, 0.6549019608, 0.0039215686, 0.6745098039215687, 0.9960784314, 0.6705882353, + 0.0039215686, 0.6784313725490196, 0.9960784314, 0.6862745098, 0.0039215686, + 0.6823529411764706, 0.9960784314, 0.7019607843, 0.0039215686, 0.6862745098039216, + 0.9960784314, 0.7176470588, 0.0039215686, 0.6901960784313725, 0.9960784314, 0.7333333333, + 0.0039215686, 0.6941176470588235, 0.9960784314, 0.7490196078, 0.0039215686, + 0.6980392156862745, 0.9960784314, 0.7607843137, 0.0039215686, 0.7019607843137254, + 0.9960784314, 0.7764705882, 0.0039215686, 0.7058823529411765, 0.9960784314, 0.7921568627, + 0.0039215686, 0.7098039215686275, 0.9960784314, 0.8078431373, 0.0039215686, + 0.7137254901960784, 0.9960784314, 0.8235294118, 0.0039215686, 0.7176470588235294, + 0.9960784314, 0.8392156863, 0.0039215686, 0.7215686274509804, 0.9960784314, 0.8549019608, + 0.0039215686, 0.7254901960784313, 0.9960784314, 0.8705882353, 0.0039215686, + 0.7294117647058823, 0.9960784314, 0.8862745098, 0.0039215686, 0.7333333333333333, + 0.9960784314, 0.9019607843, 0.0039215686, 0.7372549019607844, 0.9960784314, 0.9176470588, + 0.0039215686, 0.7411764705882353, 0.9960784314, 0.9333333333, 0.0039215686, + 0.7450980392156863, 0.9960784314, 0.9490196078, 0.0039215686, 0.7490196078431373, + 0.9960784314, 0.9647058824, 0.0039215686, 0.7529411764705882, 0.9960784314, 0.9803921569, + 0.0039215686, 0.7568627450980392, 0.9960784314, 0.9960784314, 0.0039215686, + 0.7607843137254902, 0.9960784314, 0.9960784314, 0.0196078431, 0.7647058823529411, + 0.9960784314, 0.9960784314, 0.0352941176, 0.7686274509803922, 0.9960784314, 0.9960784314, + 0.0509803922, 0.7725490196078432, 0.9960784314, 0.9960784314, 0.0666666667, + 0.7764705882352941, 0.9960784314, 0.9960784314, 0.0823529412, 0.7803921568627451, + 0.9960784314, 0.9960784314, 0.0980392157, 0.7843137254901961, 0.9960784314, 0.9960784314, + 0.1137254902, 0.788235294117647, 0.9960784314, 0.9960784314, 0.1294117647, 0.792156862745098, + 0.9960784314, 0.9960784314, 0.1450980392, 0.796078431372549, 0.9960784314, 0.9960784314, + 0.1607843137, 0.8, 0.9960784314, 0.9960784314, 0.1764705882, 0.803921568627451, 0.9960784314, + 0.9960784314, 0.1921568627, 0.807843137254902, 0.9960784314, 0.9960784314, 0.2078431373, + 0.8117647058823529, 0.9960784314, 0.9960784314, 0.2235294118, 0.8156862745098039, + 0.9960784314, 0.9960784314, 0.2392156863, 0.8196078431372549, 0.9960784314, 0.9960784314, + 0.2509803922, 0.8235294117647058, 0.9960784314, 0.9960784314, 0.2666666667, + 0.8274509803921568, 0.9960784314, 0.9960784314, 0.2823529412, 0.8313725490196079, + 0.9960784314, 0.9960784314, 0.2980392157, 0.8352941176470589, 0.9960784314, 0.9960784314, + 0.3137254902, 0.8392156862745098, 0.9960784314, 0.9960784314, 0.3333333333, + 0.8431372549019608, 0.9960784314, 0.9960784314, 0.3490196078, 0.8470588235294118, + 0.9960784314, 0.9960784314, 0.3647058824, 0.8509803921568627, 0.9960784314, 0.9960784314, + 0.3803921569, 0.8549019607843137, 0.9960784314, 0.9960784314, 0.3960784314, + 0.8588235294117647, 0.9960784314, 0.9960784314, 0.4117647059, 0.8627450980392157, + 0.9960784314, 0.9960784314, 0.4274509804, 0.8666666666666667, 0.9960784314, 0.9960784314, + 0.4431372549, 0.8705882352941177, 0.9960784314, 0.9960784314, 0.4588235294, + 0.8745098039215686, 0.9960784314, 0.9960784314, 0.4745098039, 0.8784313725490196, + 0.9960784314, 0.9960784314, 0.4901960784, 0.8823529411764706, 0.9960784314, 0.9960784314, + 0.5058823529, 0.8862745098039215, 0.9960784314, 0.9960784314, 0.5215686275, + 0.8901960784313725, 0.9960784314, 0.9960784314, 0.537254902, 0.8941176470588236, 0.9960784314, + 0.9960784314, 0.5529411765, 0.8980392156862745, 0.9960784314, 0.9960784314, 0.568627451, + 0.9019607843137255, 0.9960784314, 0.9960784314, 0.5843137255, 0.9058823529411765, + 0.9960784314, 0.9960784314, 0.6, 0.9098039215686274, 0.9960784314, 0.9960784314, 0.6156862745, + 0.9137254901960784, 0.9960784314, 0.9960784314, 0.631372549, 0.9176470588235294, 0.9960784314, + 0.9960784314, 0.6470588235, 0.9215686274509803, 0.9960784314, 0.9960784314, 0.6666666667, + 0.9254901960784314, 0.9960784314, 0.9960784314, 0.6823529412, 0.9294117647058824, + 0.9960784314, 0.9960784314, 0.6980392157, 0.9333333333333333, 0.9960784314, 0.9960784314, + 0.7137254902, 0.9372549019607843, 0.9960784314, 0.9960784314, 0.7294117647, + 0.9411764705882354, 0.9960784314, 0.9960784314, 0.7450980392, 0.9450980392156864, + 0.9960784314, 0.9960784314, 0.7568627451, 0.9490196078431372, 0.9960784314, 0.9960784314, + 0.7725490196, 0.9529411764705882, 0.9960784314, 0.9960784314, 0.7882352941, + 0.9568627450980394, 0.9960784314, 0.9960784314, 0.8039215686, 0.9607843137254903, + 0.9960784314, 0.9960784314, 0.8196078431, 0.9647058823529413, 0.9960784314, 0.9960784314, + 0.8352941176, 0.9686274509803922, 0.9960784314, 0.9960784314, 0.8509803922, + 0.9725490196078431, 0.9960784314, 0.9960784314, 0.8666666667, 0.9764705882352941, + 0.9960784314, 0.9960784314, 0.8823529412, 0.9803921568627451, 0.9960784314, 0.9960784314, + 0.8980392157, 0.984313725490196, 0.9960784314, 0.9960784314, 0.9137254902, 0.9882352941176471, + 0.9960784314, 0.9960784314, 0.9294117647, 0.9921568627450981, 0.9960784314, 0.9960784314, + 0.9450980392, 0.996078431372549, 0.9960784314, 0.9960784314, 0.9607843137, 1.0, 0.9960784314, + 0.9960784314, 0.9607843137, ], }, { ColorSpace: 'RGB', Name: 'red_hot', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0, - 0.0, - 0.0, - 0.00784313725490196, - 0.0, - 0.0, - 0.0, - 0.011764705882352941, - 0.0, - 0.0, - 0.0, - 0.01568627450980392, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.0196078431372549, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.023529411764705882, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.027450980392156862, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.03137254901960784, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.03529411764705882, - 0.0156862745, - 0.0, - 0.0, - 0.0392156862745098, - 0.0274509804, - 0.0, - 0.0, - 0.043137254901960784, - 0.0392156863, - 0.0, - 0.0, - 0.047058823529411764, - 0.0509803922, - 0.0, - 0.0, - 0.050980392156862744, - 0.062745098, - 0.0, - 0.0, - 0.054901960784313725, - 0.0784313725, - 0.0, - 0.0, - 0.05882352941176471, - 0.0901960784, - 0.0, - 0.0, - 0.06274509803921569, - 0.1058823529, - 0.0, - 0.0, - 0.06666666666666667, - 0.1176470588, - 0.0, - 0.0, - 0.07058823529411765, - 0.1294117647, - 0.0, - 0.0, - 0.07450980392156863, - 0.1411764706, - 0.0, - 0.0, - 0.0784313725490196, - 0.1529411765, - 0.0, - 0.0, - 0.08235294117647059, - 0.1647058824, - 0.0, - 0.0, - 0.08627450980392157, - 0.1764705882, - 0.0, - 0.0, - 0.09019607843137255, - 0.1882352941, - 0.0, - 0.0, - 0.09411764705882353, - 0.2039215686, - 0.0, - 0.0, - 0.09803921568627451, - 0.2156862745, - 0.0, - 0.0, - 0.10196078431372549, - 0.2274509804, - 0.0, - 0.0, - 0.10588235294117647, - 0.2392156863, - 0.0, - 0.0, - 0.10980392156862745, - 0.2549019608, - 0.0, - 0.0, - 0.11372549019607843, - 0.2666666667, - 0.0, - 0.0, - 0.11764705882352942, - 0.2784313725, - 0.0, - 0.0, - 0.12156862745098039, - 0.2901960784, - 0.0, - 0.0, - 0.12549019607843137, - 0.3058823529, - 0.0, - 0.0, - 0.12941176470588237, - 0.3176470588, - 0.0, - 0.0, - 0.13333333333333333, - 0.3294117647, - 0.0, - 0.0, - 0.13725490196078433, - 0.3411764706, - 0.0, - 0.0, - 0.1411764705882353, - 0.3529411765, - 0.0, - 0.0, - 0.1450980392156863, - 0.3647058824, - 0.0, - 0.0, - 0.14901960784313725, - 0.3764705882, - 0.0, - 0.0, - 0.15294117647058825, - 0.3882352941, - 0.0, - 0.0, - 0.1568627450980392, - 0.4039215686, - 0.0, - 0.0, - 0.1607843137254902, - 0.4156862745, - 0.0, - 0.0, - 0.16470588235294117, - 0.431372549, - 0.0, - 0.0, - 0.16862745098039217, - 0.4431372549, - 0.0, - 0.0, - 0.17254901960784313, - 0.4588235294, - 0.0, - 0.0, - 0.17647058823529413, - 0.4705882353, - 0.0, - 0.0, - 0.1803921568627451, - 0.4823529412, - 0.0, - 0.0, - 0.1843137254901961, - 0.4941176471, - 0.0, - 0.0, - 0.18823529411764706, - 0.5098039216, - 0.0, - 0.0, - 0.19215686274509805, - 0.5215686275, - 0.0, - 0.0, - 0.19607843137254902, - 0.5333333333, - 0.0, - 0.0, - 0.2, - 0.5450980392, - 0.0, - 0.0, - 0.20392156862745098, - 0.5568627451, - 0.0, - 0.0, - 0.20784313725490197, - 0.568627451, - 0.0, - 0.0, - 0.21176470588235294, - 0.5803921569, - 0.0, - 0.0, - 0.21568627450980393, - 0.5921568627, - 0.0, - 0.0, - 0.2196078431372549, - 0.6078431373, - 0.0, - 0.0, - 0.2235294117647059, - 0.6196078431, - 0.0, - 0.0, - 0.22745098039215686, - 0.631372549, - 0.0, - 0.0, - 0.23137254901960785, - 0.6431372549, - 0.0, - 0.0, - 0.23529411764705885, - 0.6588235294, - 0.0, - 0.0, - 0.23921568627450984, - 0.6705882353, - 0.0, - 0.0, - 0.24313725490196078, - 0.6823529412, - 0.0, - 0.0, - 0.24705882352941178, - 0.6941176471, - 0.0, - 0.0, - 0.25098039215686274, - 0.7098039216, - 0.0, - 0.0, - 0.2549019607843137, - 0.7215686275, - 0.0, - 0.0, - 0.25882352941176473, - 0.7333333333, - 0.0, - 0.0, - 0.2627450980392157, - 0.7450980392, - 0.0, - 0.0, - 0.26666666666666666, - 0.7568627451, - 0.0, - 0.0, - 0.27058823529411763, - 0.768627451, - 0.0, - 0.0, - 0.27450980392156865, - 0.7843137255, - 0.0, - 0.0, - 0.2784313725490196, - 0.7960784314, - 0.0, - 0.0, - 0.2823529411764706, - 0.8117647059, - 0.0, - 0.0, - 0.28627450980392155, - 0.8235294118, - 0.0, - 0.0, - 0.2901960784313726, - 0.8352941176, - 0.0, - 0.0, - 0.29411764705882354, - 0.8470588235, - 0.0, - 0.0, - 0.2980392156862745, - 0.862745098, - 0.0, - 0.0, - 0.30196078431372547, - 0.8745098039, - 0.0, - 0.0, - 0.3058823529411765, - 0.8862745098, - 0.0, - 0.0, - 0.30980392156862746, - 0.8980392157, - 0.0, - 0.0, - 0.3137254901960784, - 0.9137254902, - 0.0, - 0.0, - 0.3176470588235294, - 0.9254901961, - 0.0, - 0.0, - 0.3215686274509804, - 0.937254902, - 0.0, - 0.0, - 0.3254901960784314, - 0.9490196078, - 0.0, - 0.0, - 0.32941176470588235, - 0.9607843137, - 0.0, - 0.0, - 0.3333333333333333, - 0.968627451, - 0.0, - 0.0, - 0.33725490196078434, - 0.9803921569, - 0.0039215686, - 0.0, - 0.3411764705882353, - 0.9882352941, - 0.0078431373, - 0.0, - 0.34509803921568627, - 1.0, - 0.0117647059, - 0.0, - 0.34901960784313724, - 1.0, - 0.0235294118, - 0.0, - 0.35294117647058826, - 1.0, - 0.0352941176, - 0.0, - 0.3568627450980392, - 1.0, - 0.0470588235, - 0.0, - 0.3607843137254902, - 1.0, - 0.062745098, - 0.0, - 0.36470588235294116, - 1.0, - 0.0745098039, - 0.0, - 0.3686274509803922, - 1.0, - 0.0862745098, - 0.0, - 0.37254901960784315, - 1.0, - 0.0980392157, - 0.0, - 0.3764705882352941, - 1.0, - 0.1137254902, - 0.0, - 0.3803921568627451, - 1.0, - 0.1254901961, - 0.0, - 0.3843137254901961, - 1.0, - 0.137254902, - 0.0, - 0.38823529411764707, - 1.0, - 0.1490196078, - 0.0, - 0.39215686274509803, - 1.0, - 0.1647058824, - 0.0, - 0.396078431372549, - 1.0, - 0.1764705882, - 0.0, - 0.4, - 1.0, - 0.1882352941, - 0.0, - 0.403921568627451, - 1.0, - 0.2, - 0.0, - 0.40784313725490196, - 1.0, - 0.2156862745, - 0.0, - 0.4117647058823529, - 1.0, - 0.2274509804, - 0.0, - 0.41568627450980394, - 1.0, - 0.2392156863, - 0.0, - 0.4196078431372549, - 1.0, - 0.2509803922, - 0.0, - 0.4235294117647059, - 1.0, - 0.2666666667, - 0.0, - 0.42745098039215684, - 1.0, - 0.2784313725, - 0.0, - 0.43137254901960786, - 1.0, - 0.2901960784, - 0.0, - 0.43529411764705883, - 1.0, - 0.3019607843, - 0.0, - 0.4392156862745098, - 1.0, - 0.3176470588, - 0.0, - 0.44313725490196076, - 1.0, - 0.3294117647, - 0.0, - 0.4470588235294118, - 1.0, - 0.3411764706, - 0.0, - 0.45098039215686275, - 1.0, - 0.3529411765, - 0.0, - 0.4549019607843137, - 1.0, - 0.368627451, - 0.0, - 0.4588235294117647, - 1.0, - 0.3803921569, - 0.0, - 0.4627450980392157, - 1.0, - 0.3921568627, - 0.0, - 0.4666666666666667, - 1.0, - 0.4039215686, - 0.0, - 0.4705882352941177, - 1.0, - 0.4156862745, - 0.0, - 0.4745098039215686, - 1.0, - 0.4274509804, - 0.0, - 0.4784313725490197, - 1.0, - 0.4392156863, - 0.0, - 0.48235294117647065, - 1.0, - 0.4509803922, - 0.0, - 0.48627450980392156, - 1.0, - 0.4666666667, - 0.0, - 0.49019607843137253, - 1.0, - 0.4784313725, - 0.0, - 0.49411764705882355, - 1.0, - 0.4941176471, - 0.0, - 0.4980392156862745, - 1.0, - 0.5058823529, - 0.0, - 0.5019607843137255, - 1.0, - 0.5215686275, - 0.0, - 0.5058823529411764, - 1.0, - 0.5333333333, - 0.0, - 0.5098039215686274, - 1.0, - 0.5450980392, - 0.0, - 0.5137254901960784, - 1.0, - 0.5568627451, - 0.0, - 0.5176470588235295, - 1.0, - 0.568627451, - 0.0, - 0.5215686274509804, - 1.0, - 0.5803921569, - 0.0, - 0.5254901960784314, - 1.0, - 0.5921568627, - 0.0, - 0.5294117647058824, - 1.0, - 0.6039215686, - 0.0, - 0.5333333333333333, - 1.0, - 0.6196078431, - 0.0, - 0.5372549019607843, - 1.0, - 0.631372549, - 0.0, - 0.5411764705882353, - 1.0, - 0.6431372549, - 0.0, - 0.5450980392156862, - 1.0, - 0.6549019608, - 0.0, - 0.5490196078431373, - 1.0, - 0.6705882353, - 0.0, - 0.5529411764705883, - 1.0, - 0.6823529412, - 0.0, - 0.5568627450980392, - 1.0, - 0.6941176471, - 0.0, - 0.5607843137254902, - 1.0, - 0.7058823529, - 0.0, - 0.5647058823529412, - 1.0, - 0.7215686275, - 0.0, - 0.5686274509803921, - 1.0, - 0.7333333333, - 0.0, - 0.5725490196078431, - 1.0, - 0.7450980392, - 0.0, - 0.5764705882352941, - 1.0, - 0.7568627451, - 0.0, - 0.5803921568627451, - 1.0, - 0.7725490196, - 0.0, - 0.5843137254901961, - 1.0, - 0.7843137255, - 0.0, - 0.5882352941176471, - 1.0, - 0.7960784314, - 0.0, - 0.592156862745098, - 1.0, - 0.8078431373, - 0.0, - 0.596078431372549, - 1.0, - 0.8196078431, - 0.0, - 0.6, - 1.0, - 0.831372549, - 0.0, - 0.6039215686274509, - 1.0, - 0.8470588235, - 0.0, - 0.6078431372549019, - 1.0, - 0.8588235294, - 0.0, - 0.611764705882353, - 1.0, - 0.8745098039, - 0.0, - 0.615686274509804, - 1.0, - 0.8862745098, - 0.0, - 0.6196078431372549, - 1.0, - 0.8980392157, - 0.0, - 0.6235294117647059, - 1.0, - 0.9098039216, - 0.0, - 0.6274509803921569, - 1.0, - 0.9254901961, - 0.0, - 0.6313725490196078, - 1.0, - 0.937254902, - 0.0, - 0.6352941176470588, - 1.0, - 0.9490196078, - 0.0, - 0.6392156862745098, - 1.0, - 0.9607843137, - 0.0, - 0.6431372549019608, - 1.0, - 0.9764705882, - 0.0, - 0.6470588235294118, - 1.0, - 0.9803921569, - 0.0039215686, - 0.6509803921568628, - 1.0, - 0.9882352941, - 0.0117647059, - 0.6549019607843137, - 1.0, - 0.9921568627, - 0.0156862745, - 0.6588235294117647, - 1.0, - 1.0, - 0.0235294118, - 0.6627450980392157, - 1.0, - 1.0, - 0.0352941176, - 0.6666666666666666, - 1.0, - 1.0, - 0.0470588235, - 0.6705882352941176, - 1.0, - 1.0, - 0.0588235294, - 0.6745098039215687, - 1.0, - 1.0, - 0.0745098039, - 0.6784313725490196, - 1.0, - 1.0, - 0.0862745098, - 0.6823529411764706, - 1.0, - 1.0, - 0.0980392157, - 0.6862745098039216, - 1.0, - 1.0, - 0.1098039216, - 0.6901960784313725, - 1.0, - 1.0, - 0.1254901961, - 0.6941176470588235, - 1.0, - 1.0, - 0.137254902, - 0.6980392156862745, - 1.0, - 1.0, - 0.1490196078, - 0.7019607843137254, - 1.0, - 1.0, - 0.1607843137, - 0.7058823529411765, - 1.0, - 1.0, - 0.1764705882, - 0.7098039215686275, - 1.0, - 1.0, - 0.1882352941, - 0.7137254901960784, - 1.0, - 1.0, - 0.2, - 0.7176470588235294, - 1.0, - 1.0, - 0.2117647059, - 0.7215686274509804, - 1.0, - 1.0, - 0.2274509804, - 0.7254901960784313, - 1.0, - 1.0, - 0.2392156863, - 0.7294117647058823, - 1.0, - 1.0, - 0.2509803922, - 0.7333333333333333, - 1.0, - 1.0, - 0.262745098, - 0.7372549019607844, - 1.0, - 1.0, - 0.2784313725, - 0.7411764705882353, - 1.0, - 1.0, - 0.2901960784, - 0.7450980392156863, - 1.0, - 1.0, - 0.3019607843, - 0.7490196078431373, - 1.0, - 1.0, - 0.3137254902, - 0.7529411764705882, - 1.0, - 1.0, - 0.3294117647, - 0.7568627450980392, - 1.0, - 1.0, - 0.3411764706, - 0.7607843137254902, - 1.0, - 1.0, - 0.3529411765, - 0.7647058823529411, - 1.0, - 1.0, - 0.3647058824, - 0.7686274509803922, - 1.0, - 1.0, - 0.3803921569, - 0.7725490196078432, - 1.0, - 1.0, - 0.3921568627, - 0.7764705882352941, - 1.0, - 1.0, - 0.4039215686, - 0.7803921568627451, - 1.0, - 1.0, - 0.4156862745, - 0.7843137254901961, - 1.0, - 1.0, - 0.431372549, - 0.788235294117647, - 1.0, - 1.0, - 0.4431372549, - 0.792156862745098, - 1.0, - 1.0, - 0.4549019608, - 0.796078431372549, - 1.0, - 1.0, - 0.4666666667, - 0.8, - 1.0, - 1.0, - 0.4784313725, - 0.803921568627451, - 1.0, - 1.0, - 0.4901960784, - 0.807843137254902, - 1.0, - 1.0, - 0.5019607843, - 0.8117647058823529, - 1.0, - 1.0, - 0.5137254902, - 0.8156862745098039, - 1.0, - 1.0, - 0.5294117647, - 0.8196078431372549, - 1.0, - 1.0, - 0.5411764706, - 0.8235294117647058, - 1.0, - 1.0, - 0.5568627451, - 0.8274509803921568, - 1.0, - 1.0, - 0.568627451, - 0.8313725490196079, - 1.0, - 1.0, - 0.5843137255, - 0.8352941176470589, - 1.0, - 1.0, - 0.5960784314, - 0.8392156862745098, - 1.0, - 1.0, - 0.6078431373, - 0.8431372549019608, - 1.0, - 1.0, - 0.6196078431, - 0.8470588235294118, - 1.0, - 1.0, - 0.631372549, - 0.8509803921568627, - 1.0, - 1.0, - 0.6431372549, - 0.8549019607843137, - 1.0, - 1.0, - 0.6549019608, - 0.8588235294117647, - 1.0, - 1.0, - 0.6666666667, - 0.8627450980392157, - 1.0, - 1.0, - 0.6823529412, - 0.8666666666666667, - 1.0, - 1.0, - 0.6941176471, - 0.8705882352941177, - 1.0, - 1.0, - 0.7058823529, - 0.8745098039215686, - 1.0, - 1.0, - 0.7176470588, - 0.8784313725490196, - 1.0, - 1.0, - 0.7333333333, - 0.8823529411764706, - 1.0, - 1.0, - 0.7450980392, - 0.8862745098039215, - 1.0, - 1.0, - 0.7568627451, - 0.8901960784313725, - 1.0, - 1.0, - 0.768627451, - 0.8941176470588236, - 1.0, - 1.0, - 0.7843137255, - 0.8980392156862745, - 1.0, - 1.0, - 0.7960784314, - 0.9019607843137255, - 1.0, - 1.0, - 0.8078431373, - 0.9058823529411765, - 1.0, - 1.0, - 0.8196078431, - 0.9098039215686274, - 1.0, - 1.0, - 0.8352941176, - 0.9137254901960784, - 1.0, - 1.0, - 0.8470588235, - 0.9176470588235294, - 1.0, - 1.0, - 0.8588235294, - 0.9215686274509803, - 1.0, - 1.0, - 0.8705882353, - 0.9254901960784314, - 1.0, - 1.0, - 0.8823529412, - 0.9294117647058824, - 1.0, - 1.0, - 0.8941176471, - 0.9333333333333333, - 1.0, - 1.0, - 0.9098039216, - 0.9372549019607843, - 1.0, - 1.0, - 0.9215686275, - 0.9411764705882354, - 1.0, - 1.0, - 0.937254902, - 0.9450980392156864, - 1.0, - 1.0, - 0.9490196078, - 0.9490196078431372, - 1.0, - 1.0, - 0.9607843137, - 0.9529411764705882, - 1.0, - 1.0, - 0.9725490196, - 0.9568627450980394, - 1.0, - 1.0, - 0.9882352941, - 0.9607843137254903, - 1.0, - 1.0, - 0.9882352941, - 0.9647058823529413, - 1.0, - 1.0, - 0.9921568627, - 0.9686274509803922, - 1.0, - 1.0, - 0.9960784314, - 0.9725490196078431, - 1.0, - 1.0, - 1.0, - 0.9764705882352941, - 1.0, - 1.0, - 1.0, - 0.9803921568627451, - 1.0, - 1.0, - 1.0, - 0.984313725490196, - 1.0, - 1.0, - 1.0, - 0.9882352941176471, - 1.0, - 1.0, - 1.0, - 0.9921568627450981, - 1.0, - 1.0, - 1.0, - 0.996078431372549, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0, 0.0, 0.0, 0.00784313725490196, 0.0, 0.0, 0.0, + 0.011764705882352941, 0.0, 0.0, 0.0, 0.01568627450980392, 0.0039215686, 0.0039215686, + 0.0039215686, 0.0196078431372549, 0.0039215686, 0.0039215686, 0.0039215686, + 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0039215686, 0.027450980392156862, + 0.0039215686, 0.0039215686, 0.0039215686, 0.03137254901960784, 0.0039215686, 0.0039215686, + 0.0039215686, 0.03529411764705882, 0.0156862745, 0.0, 0.0, 0.0392156862745098, 0.0274509804, + 0.0, 0.0, 0.043137254901960784, 0.0392156863, 0.0, 0.0, 0.047058823529411764, 0.0509803922, + 0.0, 0.0, 0.050980392156862744, 0.062745098, 0.0, 0.0, 0.054901960784313725, 0.0784313725, + 0.0, 0.0, 0.05882352941176471, 0.0901960784, 0.0, 0.0, 0.06274509803921569, 0.1058823529, 0.0, + 0.0, 0.06666666666666667, 0.1176470588, 0.0, 0.0, 0.07058823529411765, 0.1294117647, 0.0, 0.0, + 0.07450980392156863, 0.1411764706, 0.0, 0.0, 0.0784313725490196, 0.1529411765, 0.0, 0.0, + 0.08235294117647059, 0.1647058824, 0.0, 0.0, 0.08627450980392157, 0.1764705882, 0.0, 0.0, + 0.09019607843137255, 0.1882352941, 0.0, 0.0, 0.09411764705882353, 0.2039215686, 0.0, 0.0, + 0.09803921568627451, 0.2156862745, 0.0, 0.0, 0.10196078431372549, 0.2274509804, 0.0, 0.0, + 0.10588235294117647, 0.2392156863, 0.0, 0.0, 0.10980392156862745, 0.2549019608, 0.0, 0.0, + 0.11372549019607843, 0.2666666667, 0.0, 0.0, 0.11764705882352942, 0.2784313725, 0.0, 0.0, + 0.12156862745098039, 0.2901960784, 0.0, 0.0, 0.12549019607843137, 0.3058823529, 0.0, 0.0, + 0.12941176470588237, 0.3176470588, 0.0, 0.0, 0.13333333333333333, 0.3294117647, 0.0, 0.0, + 0.13725490196078433, 0.3411764706, 0.0, 0.0, 0.1411764705882353, 0.3529411765, 0.0, 0.0, + 0.1450980392156863, 0.3647058824, 0.0, 0.0, 0.14901960784313725, 0.3764705882, 0.0, 0.0, + 0.15294117647058825, 0.3882352941, 0.0, 0.0, 0.1568627450980392, 0.4039215686, 0.0, 0.0, + 0.1607843137254902, 0.4156862745, 0.0, 0.0, 0.16470588235294117, 0.431372549, 0.0, 0.0, + 0.16862745098039217, 0.4431372549, 0.0, 0.0, 0.17254901960784313, 0.4588235294, 0.0, 0.0, + 0.17647058823529413, 0.4705882353, 0.0, 0.0, 0.1803921568627451, 0.4823529412, 0.0, 0.0, + 0.1843137254901961, 0.4941176471, 0.0, 0.0, 0.18823529411764706, 0.5098039216, 0.0, 0.0, + 0.19215686274509805, 0.5215686275, 0.0, 0.0, 0.19607843137254902, 0.5333333333, 0.0, 0.0, 0.2, + 0.5450980392, 0.0, 0.0, 0.20392156862745098, 0.5568627451, 0.0, 0.0, 0.20784313725490197, + 0.568627451, 0.0, 0.0, 0.21176470588235294, 0.5803921569, 0.0, 0.0, 0.21568627450980393, + 0.5921568627, 0.0, 0.0, 0.2196078431372549, 0.6078431373, 0.0, 0.0, 0.2235294117647059, + 0.6196078431, 0.0, 0.0, 0.22745098039215686, 0.631372549, 0.0, 0.0, 0.23137254901960785, + 0.6431372549, 0.0, 0.0, 0.23529411764705885, 0.6588235294, 0.0, 0.0, 0.23921568627450984, + 0.6705882353, 0.0, 0.0, 0.24313725490196078, 0.6823529412, 0.0, 0.0, 0.24705882352941178, + 0.6941176471, 0.0, 0.0, 0.25098039215686274, 0.7098039216, 0.0, 0.0, 0.2549019607843137, + 0.7215686275, 0.0, 0.0, 0.25882352941176473, 0.7333333333, 0.0, 0.0, 0.2627450980392157, + 0.7450980392, 0.0, 0.0, 0.26666666666666666, 0.7568627451, 0.0, 0.0, 0.27058823529411763, + 0.768627451, 0.0, 0.0, 0.27450980392156865, 0.7843137255, 0.0, 0.0, 0.2784313725490196, + 0.7960784314, 0.0, 0.0, 0.2823529411764706, 0.8117647059, 0.0, 0.0, 0.28627450980392155, + 0.8235294118, 0.0, 0.0, 0.2901960784313726, 0.8352941176, 0.0, 0.0, 0.29411764705882354, + 0.8470588235, 0.0, 0.0, 0.2980392156862745, 0.862745098, 0.0, 0.0, 0.30196078431372547, + 0.8745098039, 0.0, 0.0, 0.3058823529411765, 0.8862745098, 0.0, 0.0, 0.30980392156862746, + 0.8980392157, 0.0, 0.0, 0.3137254901960784, 0.9137254902, 0.0, 0.0, 0.3176470588235294, + 0.9254901961, 0.0, 0.0, 0.3215686274509804, 0.937254902, 0.0, 0.0, 0.3254901960784314, + 0.9490196078, 0.0, 0.0, 0.32941176470588235, 0.9607843137, 0.0, 0.0, 0.3333333333333333, + 0.968627451, 0.0, 0.0, 0.33725490196078434, 0.9803921569, 0.0039215686, 0.0, + 0.3411764705882353, 0.9882352941, 0.0078431373, 0.0, 0.34509803921568627, 1.0, 0.0117647059, + 0.0, 0.34901960784313724, 1.0, 0.0235294118, 0.0, 0.35294117647058826, 1.0, 0.0352941176, 0.0, + 0.3568627450980392, 1.0, 0.0470588235, 0.0, 0.3607843137254902, 1.0, 0.062745098, 0.0, + 0.36470588235294116, 1.0, 0.0745098039, 0.0, 0.3686274509803922, 1.0, 0.0862745098, 0.0, + 0.37254901960784315, 1.0, 0.0980392157, 0.0, 0.3764705882352941, 1.0, 0.1137254902, 0.0, + 0.3803921568627451, 1.0, 0.1254901961, 0.0, 0.3843137254901961, 1.0, 0.137254902, 0.0, + 0.38823529411764707, 1.0, 0.1490196078, 0.0, 0.39215686274509803, 1.0, 0.1647058824, 0.0, + 0.396078431372549, 1.0, 0.1764705882, 0.0, 0.4, 1.0, 0.1882352941, 0.0, 0.403921568627451, + 1.0, 0.2, 0.0, 0.40784313725490196, 1.0, 0.2156862745, 0.0, 0.4117647058823529, 1.0, + 0.2274509804, 0.0, 0.41568627450980394, 1.0, 0.2392156863, 0.0, 0.4196078431372549, 1.0, + 0.2509803922, 0.0, 0.4235294117647059, 1.0, 0.2666666667, 0.0, 0.42745098039215684, 1.0, + 0.2784313725, 0.0, 0.43137254901960786, 1.0, 0.2901960784, 0.0, 0.43529411764705883, 1.0, + 0.3019607843, 0.0, 0.4392156862745098, 1.0, 0.3176470588, 0.0, 0.44313725490196076, 1.0, + 0.3294117647, 0.0, 0.4470588235294118, 1.0, 0.3411764706, 0.0, 0.45098039215686275, 1.0, + 0.3529411765, 0.0, 0.4549019607843137, 1.0, 0.368627451, 0.0, 0.4588235294117647, 1.0, + 0.3803921569, 0.0, 0.4627450980392157, 1.0, 0.3921568627, 0.0, 0.4666666666666667, 1.0, + 0.4039215686, 0.0, 0.4705882352941177, 1.0, 0.4156862745, 0.0, 0.4745098039215686, 1.0, + 0.4274509804, 0.0, 0.4784313725490197, 1.0, 0.4392156863, 0.0, 0.48235294117647065, 1.0, + 0.4509803922, 0.0, 0.48627450980392156, 1.0, 0.4666666667, 0.0, 0.49019607843137253, 1.0, + 0.4784313725, 0.0, 0.49411764705882355, 1.0, 0.4941176471, 0.0, 0.4980392156862745, 1.0, + 0.5058823529, 0.0, 0.5019607843137255, 1.0, 0.5215686275, 0.0, 0.5058823529411764, 1.0, + 0.5333333333, 0.0, 0.5098039215686274, 1.0, 0.5450980392, 0.0, 0.5137254901960784, 1.0, + 0.5568627451, 0.0, 0.5176470588235295, 1.0, 0.568627451, 0.0, 0.5215686274509804, 1.0, + 0.5803921569, 0.0, 0.5254901960784314, 1.0, 0.5921568627, 0.0, 0.5294117647058824, 1.0, + 0.6039215686, 0.0, 0.5333333333333333, 1.0, 0.6196078431, 0.0, 0.5372549019607843, 1.0, + 0.631372549, 0.0, 0.5411764705882353, 1.0, 0.6431372549, 0.0, 0.5450980392156862, 1.0, + 0.6549019608, 0.0, 0.5490196078431373, 1.0, 0.6705882353, 0.0, 0.5529411764705883, 1.0, + 0.6823529412, 0.0, 0.5568627450980392, 1.0, 0.6941176471, 0.0, 0.5607843137254902, 1.0, + 0.7058823529, 0.0, 0.5647058823529412, 1.0, 0.7215686275, 0.0, 0.5686274509803921, 1.0, + 0.7333333333, 0.0, 0.5725490196078431, 1.0, 0.7450980392, 0.0, 0.5764705882352941, 1.0, + 0.7568627451, 0.0, 0.5803921568627451, 1.0, 0.7725490196, 0.0, 0.5843137254901961, 1.0, + 0.7843137255, 0.0, 0.5882352941176471, 1.0, 0.7960784314, 0.0, 0.592156862745098, 1.0, + 0.8078431373, 0.0, 0.596078431372549, 1.0, 0.8196078431, 0.0, 0.6, 1.0, 0.831372549, 0.0, + 0.6039215686274509, 1.0, 0.8470588235, 0.0, 0.6078431372549019, 1.0, 0.8588235294, 0.0, + 0.611764705882353, 1.0, 0.8745098039, 0.0, 0.615686274509804, 1.0, 0.8862745098, 0.0, + 0.6196078431372549, 1.0, 0.8980392157, 0.0, 0.6235294117647059, 1.0, 0.9098039216, 0.0, + 0.6274509803921569, 1.0, 0.9254901961, 0.0, 0.6313725490196078, 1.0, 0.937254902, 0.0, + 0.6352941176470588, 1.0, 0.9490196078, 0.0, 0.6392156862745098, 1.0, 0.9607843137, 0.0, + 0.6431372549019608, 1.0, 0.9764705882, 0.0, 0.6470588235294118, 1.0, 0.9803921569, + 0.0039215686, 0.6509803921568628, 1.0, 0.9882352941, 0.0117647059, 0.6549019607843137, 1.0, + 0.9921568627, 0.0156862745, 0.6588235294117647, 1.0, 1.0, 0.0235294118, 0.6627450980392157, + 1.0, 1.0, 0.0352941176, 0.6666666666666666, 1.0, 1.0, 0.0470588235, 0.6705882352941176, 1.0, + 1.0, 0.0588235294, 0.6745098039215687, 1.0, 1.0, 0.0745098039, 0.6784313725490196, 1.0, 1.0, + 0.0862745098, 0.6823529411764706, 1.0, 1.0, 0.0980392157, 0.6862745098039216, 1.0, 1.0, + 0.1098039216, 0.6901960784313725, 1.0, 1.0, 0.1254901961, 0.6941176470588235, 1.0, 1.0, + 0.137254902, 0.6980392156862745, 1.0, 1.0, 0.1490196078, 0.7019607843137254, 1.0, 1.0, + 0.1607843137, 0.7058823529411765, 1.0, 1.0, 0.1764705882, 0.7098039215686275, 1.0, 1.0, + 0.1882352941, 0.7137254901960784, 1.0, 1.0, 0.2, 0.7176470588235294, 1.0, 1.0, 0.2117647059, + 0.7215686274509804, 1.0, 1.0, 0.2274509804, 0.7254901960784313, 1.0, 1.0, 0.2392156863, + 0.7294117647058823, 1.0, 1.0, 0.2509803922, 0.7333333333333333, 1.0, 1.0, 0.262745098, + 0.7372549019607844, 1.0, 1.0, 0.2784313725, 0.7411764705882353, 1.0, 1.0, 0.2901960784, + 0.7450980392156863, 1.0, 1.0, 0.3019607843, 0.7490196078431373, 1.0, 1.0, 0.3137254902, + 0.7529411764705882, 1.0, 1.0, 0.3294117647, 0.7568627450980392, 1.0, 1.0, 0.3411764706, + 0.7607843137254902, 1.0, 1.0, 0.3529411765, 0.7647058823529411, 1.0, 1.0, 0.3647058824, + 0.7686274509803922, 1.0, 1.0, 0.3803921569, 0.7725490196078432, 1.0, 1.0, 0.3921568627, + 0.7764705882352941, 1.0, 1.0, 0.4039215686, 0.7803921568627451, 1.0, 1.0, 0.4156862745, + 0.7843137254901961, 1.0, 1.0, 0.431372549, 0.788235294117647, 1.0, 1.0, 0.4431372549, + 0.792156862745098, 1.0, 1.0, 0.4549019608, 0.796078431372549, 1.0, 1.0, 0.4666666667, 0.8, + 1.0, 1.0, 0.4784313725, 0.803921568627451, 1.0, 1.0, 0.4901960784, 0.807843137254902, 1.0, + 1.0, 0.5019607843, 0.8117647058823529, 1.0, 1.0, 0.5137254902, 0.8156862745098039, 1.0, 1.0, + 0.5294117647, 0.8196078431372549, 1.0, 1.0, 0.5411764706, 0.8235294117647058, 1.0, 1.0, + 0.5568627451, 0.8274509803921568, 1.0, 1.0, 0.568627451, 0.8313725490196079, 1.0, 1.0, + 0.5843137255, 0.8352941176470589, 1.0, 1.0, 0.5960784314, 0.8392156862745098, 1.0, 1.0, + 0.6078431373, 0.8431372549019608, 1.0, 1.0, 0.6196078431, 0.8470588235294118, 1.0, 1.0, + 0.631372549, 0.8509803921568627, 1.0, 1.0, 0.6431372549, 0.8549019607843137, 1.0, 1.0, + 0.6549019608, 0.8588235294117647, 1.0, 1.0, 0.6666666667, 0.8627450980392157, 1.0, 1.0, + 0.6823529412, 0.8666666666666667, 1.0, 1.0, 0.6941176471, 0.8705882352941177, 1.0, 1.0, + 0.7058823529, 0.8745098039215686, 1.0, 1.0, 0.7176470588, 0.8784313725490196, 1.0, 1.0, + 0.7333333333, 0.8823529411764706, 1.0, 1.0, 0.7450980392, 0.8862745098039215, 1.0, 1.0, + 0.7568627451, 0.8901960784313725, 1.0, 1.0, 0.768627451, 0.8941176470588236, 1.0, 1.0, + 0.7843137255, 0.8980392156862745, 1.0, 1.0, 0.7960784314, 0.9019607843137255, 1.0, 1.0, + 0.8078431373, 0.9058823529411765, 1.0, 1.0, 0.8196078431, 0.9098039215686274, 1.0, 1.0, + 0.8352941176, 0.9137254901960784, 1.0, 1.0, 0.8470588235, 0.9176470588235294, 1.0, 1.0, + 0.8588235294, 0.9215686274509803, 1.0, 1.0, 0.8705882353, 0.9254901960784314, 1.0, 1.0, + 0.8823529412, 0.9294117647058824, 1.0, 1.0, 0.8941176471, 0.9333333333333333, 1.0, 1.0, + 0.9098039216, 0.9372549019607843, 1.0, 1.0, 0.9215686275, 0.9411764705882354, 1.0, 1.0, + 0.937254902, 0.9450980392156864, 1.0, 1.0, 0.9490196078, 0.9490196078431372, 1.0, 1.0, + 0.9607843137, 0.9529411764705882, 1.0, 1.0, 0.9725490196, 0.9568627450980394, 1.0, 1.0, + 0.9882352941, 0.9607843137254903, 1.0, 1.0, 0.9882352941, 0.9647058823529413, 1.0, 1.0, + 0.9921568627, 0.9686274509803922, 1.0, 1.0, 0.9960784314, 0.9725490196078431, 1.0, 1.0, 1.0, + 0.9764705882352941, 1.0, 1.0, 1.0, 0.9803921568627451, 1.0, 1.0, 1.0, 0.984313725490196, 1.0, + 1.0, 1.0, 0.9882352941176471, 1.0, 1.0, 1.0, 0.9921568627450981, 1.0, 1.0, 1.0, + 0.996078431372549, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ], }, { ColorSpace: 'RGB', Name: 's_pet', RGBPoints: [ - 0.0, - 0.0156862745, - 0.0039215686, - 0.0156862745, - 0.00392156862745098, - 0.0156862745, - 0.0039215686, - 0.0156862745, - 0.00784313725490196, - 0.0274509804, - 0.0039215686, - 0.031372549, - 0.011764705882352941, - 0.0352941176, - 0.0039215686, - 0.0509803922, - 0.01568627450980392, - 0.0392156863, - 0.0039215686, - 0.0666666667, - 0.0196078431372549, - 0.0509803922, - 0.0039215686, - 0.0823529412, - 0.023529411764705882, - 0.062745098, - 0.0039215686, - 0.0980392157, - 0.027450980392156862, - 0.0705882353, - 0.0039215686, - 0.1176470588, - 0.03137254901960784, - 0.0745098039, - 0.0039215686, - 0.1333333333, - 0.03529411764705882, - 0.0862745098, - 0.0039215686, - 0.1490196078, - 0.0392156862745098, - 0.0980392157, - 0.0039215686, - 0.1647058824, - 0.043137254901960784, - 0.1058823529, - 0.0039215686, - 0.1843137255, - 0.047058823529411764, - 0.1098039216, - 0.0039215686, - 0.2, - 0.050980392156862744, - 0.1215686275, - 0.0039215686, - 0.2156862745, - 0.054901960784313725, - 0.1333333333, - 0.0039215686, - 0.231372549, - 0.05882352941176471, - 0.137254902, - 0.0039215686, - 0.2509803922, - 0.06274509803921569, - 0.1490196078, - 0.0039215686, - 0.262745098, - 0.06666666666666667, - 0.1607843137, - 0.0039215686, - 0.2784313725, - 0.07058823529411765, - 0.168627451, - 0.0039215686, - 0.2941176471, - 0.07450980392156863, - 0.1725490196, - 0.0039215686, - 0.3137254902, - 0.0784313725490196, - 0.1843137255, - 0.0039215686, - 0.3294117647, - 0.08235294117647059, - 0.1960784314, - 0.0039215686, - 0.3450980392, - 0.08627450980392157, - 0.2039215686, - 0.0039215686, - 0.3607843137, - 0.09019607843137255, - 0.2078431373, - 0.0039215686, - 0.3803921569, - 0.09411764705882353, - 0.2196078431, - 0.0039215686, - 0.3960784314, - 0.09803921568627451, - 0.231372549, - 0.0039215686, - 0.4117647059, - 0.10196078431372549, - 0.2392156863, - 0.0039215686, - 0.4274509804, - 0.10588235294117647, - 0.2431372549, - 0.0039215686, - 0.4470588235, - 0.10980392156862745, - 0.2509803922, - 0.0039215686, - 0.462745098, - 0.11372549019607843, - 0.262745098, - 0.0039215686, - 0.4784313725, - 0.11764705882352942, - 0.2666666667, - 0.0039215686, - 0.4980392157, - 0.12156862745098039, - 0.2666666667, - 0.0039215686, - 0.4980392157, - 0.12549019607843137, - 0.262745098, - 0.0039215686, - 0.5137254902, - 0.12941176470588237, - 0.2509803922, - 0.0039215686, - 0.5294117647, - 0.13333333333333333, - 0.2431372549, - 0.0039215686, - 0.5450980392, - 0.13725490196078433, - 0.2392156863, - 0.0039215686, - 0.5607843137, - 0.1411764705882353, - 0.231372549, - 0.0039215686, - 0.5764705882, - 0.1450980392156863, - 0.2196078431, - 0.0039215686, - 0.5921568627, - 0.14901960784313725, - 0.2078431373, - 0.0039215686, - 0.6078431373, - 0.15294117647058825, - 0.2039215686, - 0.0039215686, - 0.6235294118, - 0.1568627450980392, - 0.1960784314, - 0.0039215686, - 0.6392156863, - 0.1607843137254902, - 0.1843137255, - 0.0039215686, - 0.6549019608, - 0.16470588235294117, - 0.1725490196, - 0.0039215686, - 0.6705882353, - 0.16862745098039217, - 0.168627451, - 0.0039215686, - 0.6862745098, - 0.17254901960784313, - 0.1607843137, - 0.0039215686, - 0.7019607843, - 0.17647058823529413, - 0.1490196078, - 0.0039215686, - 0.7176470588, - 0.1803921568627451, - 0.137254902, - 0.0039215686, - 0.7333333333, - 0.1843137254901961, - 0.1333333333, - 0.0039215686, - 0.7490196078, - 0.18823529411764706, - 0.1215686275, - 0.0039215686, - 0.7607843137, - 0.19215686274509805, - 0.1098039216, - 0.0039215686, - 0.7764705882, - 0.19607843137254902, - 0.1058823529, - 0.0039215686, - 0.7921568627, - 0.2, - 0.0980392157, - 0.0039215686, - 0.8078431373, - 0.20392156862745098, - 0.0862745098, - 0.0039215686, - 0.8235294118, - 0.20784313725490197, - 0.0745098039, - 0.0039215686, - 0.8392156863, - 0.21176470588235294, - 0.0705882353, - 0.0039215686, - 0.8549019608, - 0.21568627450980393, - 0.062745098, - 0.0039215686, - 0.8705882353, - 0.2196078431372549, - 0.0509803922, - 0.0039215686, - 0.8862745098, - 0.2235294117647059, - 0.0392156863, - 0.0039215686, - 0.9019607843, - 0.22745098039215686, - 0.0352941176, - 0.0039215686, - 0.9176470588, - 0.23137254901960785, - 0.0274509804, - 0.0039215686, - 0.9333333333, - 0.23529411764705885, - 0.0156862745, - 0.0039215686, - 0.9490196078, - 0.23921568627450984, - 0.0078431373, - 0.0039215686, - 0.9647058824, - 0.24313725490196078, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.24705882352941178, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.25098039215686274, - 0.0039215686, - 0.0196078431, - 0.9647058824, - 0.2549019607843137, - 0.0039215686, - 0.0392156863, - 0.9490196078, - 0.25882352941176473, - 0.0039215686, - 0.0549019608, - 0.9333333333, - 0.2627450980392157, - 0.0039215686, - 0.0745098039, - 0.9176470588, - 0.26666666666666666, - 0.0039215686, - 0.0901960784, - 0.9019607843, - 0.27058823529411763, - 0.0039215686, - 0.1098039216, - 0.8862745098, - 0.27450980392156865, - 0.0039215686, - 0.1254901961, - 0.8705882353, - 0.2784313725490196, - 0.0039215686, - 0.1450980392, - 0.8549019608, - 0.2823529411764706, - 0.0039215686, - 0.1607843137, - 0.8392156863, - 0.28627450980392155, - 0.0039215686, - 0.1803921569, - 0.8235294118, - 0.2901960784313726, - 0.0039215686, - 0.1960784314, - 0.8078431373, - 0.29411764705882354, - 0.0039215686, - 0.2156862745, - 0.7921568627, - 0.2980392156862745, - 0.0039215686, - 0.231372549, - 0.7764705882, - 0.30196078431372547, - 0.0039215686, - 0.2509803922, - 0.7607843137, - 0.3058823529411765, - 0.0039215686, - 0.262745098, - 0.7490196078, - 0.30980392156862746, - 0.0039215686, - 0.2823529412, - 0.7333333333, - 0.3137254901960784, - 0.0039215686, - 0.2980392157, - 0.7176470588, - 0.3176470588235294, - 0.0039215686, - 0.3176470588, - 0.7019607843, - 0.3215686274509804, - 0.0039215686, - 0.3333333333, - 0.6862745098, - 0.3254901960784314, - 0.0039215686, - 0.3529411765, - 0.6705882353, - 0.32941176470588235, - 0.0039215686, - 0.368627451, - 0.6549019608, - 0.3333333333333333, - 0.0039215686, - 0.3882352941, - 0.6392156863, - 0.33725490196078434, - 0.0039215686, - 0.4039215686, - 0.6235294118, - 0.3411764705882353, - 0.0039215686, - 0.4235294118, - 0.6078431373, - 0.34509803921568627, - 0.0039215686, - 0.4392156863, - 0.5921568627, - 0.34901960784313724, - 0.0039215686, - 0.4588235294, - 0.5764705882, - 0.35294117647058826, - 0.0039215686, - 0.4745098039, - 0.5607843137, - 0.3568627450980392, - 0.0039215686, - 0.4941176471, - 0.5450980392, - 0.3607843137254902, - 0.0039215686, - 0.5098039216, - 0.5294117647, - 0.36470588235294116, - 0.0039215686, - 0.5294117647, - 0.5137254902, - 0.3686274509803922, - 0.0039215686, - 0.5450980392, - 0.4980392157, - 0.37254901960784315, - 0.0039215686, - 0.5647058824, - 0.4784313725, - 0.3764705882352941, - 0.0039215686, - 0.5803921569, - 0.462745098, - 0.3803921568627451, - 0.0039215686, - 0.6, - 0.4470588235, - 0.3843137254901961, - 0.0039215686, - 0.6156862745, - 0.4274509804, - 0.38823529411764707, - 0.0039215686, - 0.6352941176, - 0.4117647059, - 0.39215686274509803, - 0.0039215686, - 0.6509803922, - 0.3960784314, - 0.396078431372549, - 0.0039215686, - 0.6705882353, - 0.3803921569, - 0.4, - 0.0039215686, - 0.6862745098, - 0.3607843137, - 0.403921568627451, - 0.0039215686, - 0.7058823529, - 0.3450980392, - 0.40784313725490196, - 0.0039215686, - 0.7215686275, - 0.3294117647, - 0.4117647058823529, - 0.0039215686, - 0.7411764706, - 0.3137254902, - 0.41568627450980394, - 0.0039215686, - 0.7529411765, - 0.2941176471, - 0.4196078431372549, - 0.0039215686, - 0.7960784314, - 0.2784313725, - 0.4235294117647059, - 0.0039215686, - 0.7960784314, - 0.262745098, - 0.42745098039215684, - 0.0392156863, - 0.8039215686, - 0.2509803922, - 0.43137254901960786, - 0.0745098039, - 0.8117647059, - 0.231372549, - 0.43529411764705883, - 0.1098039216, - 0.8196078431, - 0.2156862745, - 0.4392156862745098, - 0.1450980392, - 0.8274509804, - 0.2, - 0.44313725490196076, - 0.1803921569, - 0.8352941176, - 0.1843137255, - 0.4470588235294118, - 0.2156862745, - 0.8431372549, - 0.1647058824, - 0.45098039215686275, - 0.2509803922, - 0.8509803922, - 0.1490196078, - 0.4549019607843137, - 0.2823529412, - 0.8588235294, - 0.1333333333, - 0.4588235294117647, - 0.3176470588, - 0.8666666667, - 0.1176470588, - 0.4627450980392157, - 0.3529411765, - 0.8745098039, - 0.0980392157, - 0.4666666666666667, - 0.3882352941, - 0.8823529412, - 0.0823529412, - 0.4705882352941177, - 0.4235294118, - 0.8901960784, - 0.0666666667, - 0.4745098039215686, - 0.4588235294, - 0.8980392157, - 0.0509803922, - 0.4784313725490197, - 0.4941176471, - 0.9058823529, - 0.0431372549, - 0.48235294117647065, - 0.5294117647, - 0.9137254902, - 0.031372549, - 0.48627450980392156, - 0.5647058824, - 0.9215686275, - 0.0196078431, - 0.49019607843137253, - 0.6, - 0.9294117647, - 0.0078431373, - 0.49411764705882355, - 0.6352941176, - 0.937254902, - 0.0039215686, - 0.4980392156862745, - 0.6705882353, - 0.9450980392, - 0.0039215686, - 0.5019607843137255, - 0.7058823529, - 0.9490196078, - 0.0039215686, - 0.5058823529411764, - 0.7411764706, - 0.9568627451, - 0.0039215686, - 0.5098039215686274, - 0.7725490196, - 0.9607843137, - 0.0039215686, - 0.5137254901960784, - 0.8078431373, - 0.968627451, - 0.0039215686, - 0.5176470588235295, - 0.8431372549, - 0.9725490196, - 0.0039215686, - 0.5215686274509804, - 0.8784313725, - 0.9803921569, - 0.0039215686, - 0.5254901960784314, - 0.9137254902, - 0.9843137255, - 0.0039215686, - 0.5294117647058824, - 0.9490196078, - 0.9921568627, - 0.0039215686, - 0.5333333333333333, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.5372549019607843, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.5411764705882353, - 0.9960784314, - 0.9921568627, - 0.0039215686, - 0.5450980392156862, - 0.9960784314, - 0.9843137255, - 0.0039215686, - 0.5490196078431373, - 0.9960784314, - 0.9764705882, - 0.0039215686, - 0.5529411764705883, - 0.9960784314, - 0.968627451, - 0.0039215686, - 0.5568627450980392, - 0.9960784314, - 0.9607843137, - 0.0039215686, - 0.5607843137254902, - 0.9960784314, - 0.9529411765, - 0.0039215686, - 0.5647058823529412, - 0.9960784314, - 0.9450980392, - 0.0039215686, - 0.5686274509803921, - 0.9960784314, - 0.937254902, - 0.0039215686, - 0.5725490196078431, - 0.9960784314, - 0.9294117647, - 0.0039215686, - 0.5764705882352941, - 0.9960784314, - 0.9215686275, - 0.0039215686, - 0.5803921568627451, - 0.9960784314, - 0.9137254902, - 0.0039215686, - 0.5843137254901961, - 0.9960784314, - 0.9058823529, - 0.0039215686, - 0.5882352941176471, - 0.9960784314, - 0.8980392157, - 0.0039215686, - 0.592156862745098, - 0.9960784314, - 0.8901960784, - 0.0039215686, - 0.596078431372549, - 0.9960784314, - 0.8823529412, - 0.0039215686, - 0.6, - 0.9960784314, - 0.8745098039, - 0.0039215686, - 0.6039215686274509, - 0.9960784314, - 0.8666666667, - 0.0039215686, - 0.6078431372549019, - 0.9960784314, - 0.8588235294, - 0.0039215686, - 0.611764705882353, - 0.9960784314, - 0.8509803922, - 0.0039215686, - 0.615686274509804, - 0.9960784314, - 0.8431372549, - 0.0039215686, - 0.6196078431372549, - 0.9960784314, - 0.8352941176, - 0.0039215686, - 0.6235294117647059, - 0.9960784314, - 0.8274509804, - 0.0039215686, - 0.6274509803921569, - 0.9960784314, - 0.8196078431, - 0.0039215686, - 0.6313725490196078, - 0.9960784314, - 0.8117647059, - 0.0039215686, - 0.6352941176470588, - 0.9960784314, - 0.8039215686, - 0.0039215686, - 0.6392156862745098, - 0.9960784314, - 0.7960784314, - 0.0039215686, - 0.6431372549019608, - 0.9960784314, - 0.7882352941, - 0.0039215686, - 0.6470588235294118, - 0.9960784314, - 0.7803921569, - 0.0039215686, - 0.6509803921568628, - 0.9960784314, - 0.7725490196, - 0.0039215686, - 0.6549019607843137, - 0.9960784314, - 0.7647058824, - 0.0039215686, - 0.6588235294117647, - 0.9960784314, - 0.7568627451, - 0.0039215686, - 0.6627450980392157, - 0.9960784314, - 0.7490196078, - 0.0039215686, - 0.6666666666666666, - 0.9960784314, - 0.7450980392, - 0.0039215686, - 0.6705882352941176, - 0.9960784314, - 0.737254902, - 0.0039215686, - 0.6745098039215687, - 0.9960784314, - 0.7294117647, - 0.0039215686, - 0.6784313725490196, - 0.9960784314, - 0.7215686275, - 0.0039215686, - 0.6823529411764706, - 0.9960784314, - 0.7137254902, - 0.0039215686, - 0.6862745098039216, - 0.9960784314, - 0.7058823529, - 0.0039215686, - 0.6901960784313725, - 0.9960784314, - 0.6980392157, - 0.0039215686, - 0.6941176470588235, - 0.9960784314, - 0.6901960784, - 0.0039215686, - 0.6980392156862745, - 0.9960784314, - 0.6823529412, - 0.0039215686, - 0.7019607843137254, - 0.9960784314, - 0.6745098039, - 0.0039215686, - 0.7058823529411765, - 0.9960784314, - 0.6666666667, - 0.0039215686, - 0.7098039215686275, - 0.9960784314, - 0.6588235294, - 0.0039215686, - 0.7137254901960784, - 0.9960784314, - 0.6509803922, - 0.0039215686, - 0.7176470588235294, - 0.9960784314, - 0.6431372549, - 0.0039215686, - 0.7215686274509804, - 0.9960784314, - 0.6352941176, - 0.0039215686, - 0.7254901960784313, - 0.9960784314, - 0.6274509804, - 0.0039215686, - 0.7294117647058823, - 0.9960784314, - 0.6196078431, - 0.0039215686, - 0.7333333333333333, - 0.9960784314, - 0.6117647059, - 0.0039215686, - 0.7372549019607844, - 0.9960784314, - 0.6039215686, - 0.0039215686, - 0.7411764705882353, - 0.9960784314, - 0.5960784314, - 0.0039215686, - 0.7450980392156863, - 0.9960784314, - 0.5882352941, - 0.0039215686, - 0.7490196078431373, - 0.9960784314, - 0.5803921569, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.5725490196, - 0.0039215686, - 0.7568627450980392, - 0.9960784314, - 0.5647058824, - 0.0039215686, - 0.7607843137254902, - 0.9960784314, - 0.5568627451, - 0.0039215686, - 0.7647058823529411, - 0.9960784314, - 0.5490196078, - 0.0039215686, - 0.7686274509803922, - 0.9960784314, - 0.5411764706, - 0.0039215686, - 0.7725490196078432, - 0.9960784314, - 0.5333333333, - 0.0039215686, - 0.7764705882352941, - 0.9960784314, - 0.5254901961, - 0.0039215686, - 0.7803921568627451, - 0.9960784314, - 0.5176470588, - 0.0039215686, - 0.7843137254901961, - 0.9960784314, - 0.5098039216, - 0.0039215686, - 0.788235294117647, - 0.9960784314, - 0.5019607843, - 0.0039215686, - 0.792156862745098, - 0.9960784314, - 0.4941176471, - 0.0039215686, - 0.796078431372549, - 0.9960784314, - 0.4862745098, - 0.0039215686, - 0.8, - 0.9960784314, - 0.4784313725, - 0.0039215686, - 0.803921568627451, - 0.9960784314, - 0.4705882353, - 0.0039215686, - 0.807843137254902, - 0.9960784314, - 0.462745098, - 0.0039215686, - 0.8117647058823529, - 0.9960784314, - 0.4549019608, - 0.0039215686, - 0.8156862745098039, - 0.9960784314, - 0.4470588235, - 0.0039215686, - 0.8196078431372549, - 0.9960784314, - 0.4392156863, - 0.0039215686, - 0.8235294117647058, - 0.9960784314, - 0.431372549, - 0.0039215686, - 0.8274509803921568, - 0.9960784314, - 0.4235294118, - 0.0039215686, - 0.8313725490196079, - 0.9960784314, - 0.4156862745, - 0.0039215686, - 0.8352941176470589, - 0.9960784314, - 0.4078431373, - 0.0039215686, - 0.8392156862745098, - 0.9960784314, - 0.4, - 0.0039215686, - 0.8431372549019608, - 0.9960784314, - 0.3921568627, - 0.0039215686, - 0.8470588235294118, - 0.9960784314, - 0.3843137255, - 0.0039215686, - 0.8509803921568627, - 0.9960784314, - 0.3764705882, - 0.0039215686, - 0.8549019607843137, - 0.9960784314, - 0.368627451, - 0.0039215686, - 0.8588235294117647, - 0.9960784314, - 0.3607843137, - 0.0039215686, - 0.8627450980392157, - 0.9960784314, - 0.3529411765, - 0.0039215686, - 0.8666666666666667, - 0.9960784314, - 0.3450980392, - 0.0039215686, - 0.8705882352941177, - 0.9960784314, - 0.337254902, - 0.0039215686, - 0.8745098039215686, - 0.9960784314, - 0.3294117647, - 0.0039215686, - 0.8784313725490196, - 0.9960784314, - 0.3215686275, - 0.0039215686, - 0.8823529411764706, - 0.9960784314, - 0.3137254902, - 0.0039215686, - 0.8862745098039215, - 0.9960784314, - 0.3058823529, - 0.0039215686, - 0.8901960784313725, - 0.9960784314, - 0.2980392157, - 0.0039215686, - 0.8941176470588236, - 0.9960784314, - 0.2901960784, - 0.0039215686, - 0.8980392156862745, - 0.9960784314, - 0.2823529412, - 0.0039215686, - 0.9019607843137255, - 0.9960784314, - 0.2705882353, - 0.0039215686, - 0.9058823529411765, - 0.9960784314, - 0.2588235294, - 0.0039215686, - 0.9098039215686274, - 0.9960784314, - 0.2509803922, - 0.0039215686, - 0.9137254901960784, - 0.9960784314, - 0.2431372549, - 0.0039215686, - 0.9176470588235294, - 0.9960784314, - 0.231372549, - 0.0039215686, - 0.9215686274509803, - 0.9960784314, - 0.2196078431, - 0.0039215686, - 0.9254901960784314, - 0.9960784314, - 0.2117647059, - 0.0039215686, - 0.9294117647058824, - 0.9960784314, - 0.2, - 0.0039215686, - 0.9333333333333333, - 0.9960784314, - 0.1882352941, - 0.0039215686, - 0.9372549019607843, - 0.9960784314, - 0.1764705882, - 0.0039215686, - 0.9411764705882354, - 0.9960784314, - 0.168627451, - 0.0039215686, - 0.9450980392156864, - 0.9960784314, - 0.1568627451, - 0.0039215686, - 0.9490196078431372, - 0.9960784314, - 0.1450980392, - 0.0039215686, - 0.9529411764705882, - 0.9960784314, - 0.1333333333, - 0.0039215686, - 0.9568627450980394, - 0.9960784314, - 0.1254901961, - 0.0039215686, - 0.9607843137254903, - 0.9960784314, - 0.1137254902, - 0.0039215686, - 0.9647058823529413, - 0.9960784314, - 0.1019607843, - 0.0039215686, - 0.9686274509803922, - 0.9960784314, - 0.0901960784, - 0.0039215686, - 0.9725490196078431, - 0.9960784314, - 0.0823529412, - 0.0039215686, - 0.9764705882352941, - 0.9960784314, - 0.0705882353, - 0.0039215686, - 0.9803921568627451, - 0.9960784314, - 0.0588235294, - 0.0039215686, - 0.984313725490196, - 0.9960784314, - 0.0470588235, - 0.0039215686, - 0.9882352941176471, - 0.9960784314, - 0.0392156863, - 0.0039215686, - 0.9921568627450981, - 0.9960784314, - 0.0274509804, - 0.0039215686, - 0.996078431372549, - 0.9960784314, - 0.0156862745, - 0.0039215686, - 1.0, - 0.9960784314, - 0.0156862745, - 0.0039215686, + 0.0, 0.0156862745, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0156862745, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0274509804, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0352941176, 0.0039215686, 0.0509803922, 0.01568627450980392, + 0.0392156863, 0.0039215686, 0.0666666667, 0.0196078431372549, 0.0509803922, 0.0039215686, + 0.0823529412, 0.023529411764705882, 0.062745098, 0.0039215686, 0.0980392157, + 0.027450980392156862, 0.0705882353, 0.0039215686, 0.1176470588, 0.03137254901960784, + 0.0745098039, 0.0039215686, 0.1333333333, 0.03529411764705882, 0.0862745098, 0.0039215686, + 0.1490196078, 0.0392156862745098, 0.0980392157, 0.0039215686, 0.1647058824, + 0.043137254901960784, 0.1058823529, 0.0039215686, 0.1843137255, 0.047058823529411764, + 0.1098039216, 0.0039215686, 0.2, 0.050980392156862744, 0.1215686275, 0.0039215686, + 0.2156862745, 0.054901960784313725, 0.1333333333, 0.0039215686, 0.231372549, + 0.05882352941176471, 0.137254902, 0.0039215686, 0.2509803922, 0.06274509803921569, + 0.1490196078, 0.0039215686, 0.262745098, 0.06666666666666667, 0.1607843137, 0.0039215686, + 0.2784313725, 0.07058823529411765, 0.168627451, 0.0039215686, 0.2941176471, + 0.07450980392156863, 0.1725490196, 0.0039215686, 0.3137254902, 0.0784313725490196, + 0.1843137255, 0.0039215686, 0.3294117647, 0.08235294117647059, 0.1960784314, 0.0039215686, + 0.3450980392, 0.08627450980392157, 0.2039215686, 0.0039215686, 0.3607843137, + 0.09019607843137255, 0.2078431373, 0.0039215686, 0.3803921569, 0.09411764705882353, + 0.2196078431, 0.0039215686, 0.3960784314, 0.09803921568627451, 0.231372549, 0.0039215686, + 0.4117647059, 0.10196078431372549, 0.2392156863, 0.0039215686, 0.4274509804, + 0.10588235294117647, 0.2431372549, 0.0039215686, 0.4470588235, 0.10980392156862745, + 0.2509803922, 0.0039215686, 0.462745098, 0.11372549019607843, 0.262745098, 0.0039215686, + 0.4784313725, 0.11764705882352942, 0.2666666667, 0.0039215686, 0.4980392157, + 0.12156862745098039, 0.2666666667, 0.0039215686, 0.4980392157, 0.12549019607843137, + 0.262745098, 0.0039215686, 0.5137254902, 0.12941176470588237, 0.2509803922, 0.0039215686, + 0.5294117647, 0.13333333333333333, 0.2431372549, 0.0039215686, 0.5450980392, + 0.13725490196078433, 0.2392156863, 0.0039215686, 0.5607843137, 0.1411764705882353, + 0.231372549, 0.0039215686, 0.5764705882, 0.1450980392156863, 0.2196078431, 0.0039215686, + 0.5921568627, 0.14901960784313725, 0.2078431373, 0.0039215686, 0.6078431373, + 0.15294117647058825, 0.2039215686, 0.0039215686, 0.6235294118, 0.1568627450980392, + 0.1960784314, 0.0039215686, 0.6392156863, 0.1607843137254902, 0.1843137255, 0.0039215686, + 0.6549019608, 0.16470588235294117, 0.1725490196, 0.0039215686, 0.6705882353, + 0.16862745098039217, 0.168627451, 0.0039215686, 0.6862745098, 0.17254901960784313, + 0.1607843137, 0.0039215686, 0.7019607843, 0.17647058823529413, 0.1490196078, 0.0039215686, + 0.7176470588, 0.1803921568627451, 0.137254902, 0.0039215686, 0.7333333333, 0.1843137254901961, + 0.1333333333, 0.0039215686, 0.7490196078, 0.18823529411764706, 0.1215686275, 0.0039215686, + 0.7607843137, 0.19215686274509805, 0.1098039216, 0.0039215686, 0.7764705882, + 0.19607843137254902, 0.1058823529, 0.0039215686, 0.7921568627, 0.2, 0.0980392157, + 0.0039215686, 0.8078431373, 0.20392156862745098, 0.0862745098, 0.0039215686, 0.8235294118, + 0.20784313725490197, 0.0745098039, 0.0039215686, 0.8392156863, 0.21176470588235294, + 0.0705882353, 0.0039215686, 0.8549019608, 0.21568627450980393, 0.062745098, 0.0039215686, + 0.8705882353, 0.2196078431372549, 0.0509803922, 0.0039215686, 0.8862745098, + 0.2235294117647059, 0.0392156863, 0.0039215686, 0.9019607843, 0.22745098039215686, + 0.0352941176, 0.0039215686, 0.9176470588, 0.23137254901960785, 0.0274509804, 0.0039215686, + 0.9333333333, 0.23529411764705885, 0.0156862745, 0.0039215686, 0.9490196078, + 0.23921568627450984, 0.0078431373, 0.0039215686, 0.9647058824, 0.24313725490196078, + 0.0039215686, 0.0039215686, 0.9960784314, 0.24705882352941178, 0.0039215686, 0.0039215686, + 0.9960784314, 0.25098039215686274, 0.0039215686, 0.0196078431, 0.9647058824, + 0.2549019607843137, 0.0039215686, 0.0392156863, 0.9490196078, 0.25882352941176473, + 0.0039215686, 0.0549019608, 0.9333333333, 0.2627450980392157, 0.0039215686, 0.0745098039, + 0.9176470588, 0.26666666666666666, 0.0039215686, 0.0901960784, 0.9019607843, + 0.27058823529411763, 0.0039215686, 0.1098039216, 0.8862745098, 0.27450980392156865, + 0.0039215686, 0.1254901961, 0.8705882353, 0.2784313725490196, 0.0039215686, 0.1450980392, + 0.8549019608, 0.2823529411764706, 0.0039215686, 0.1607843137, 0.8392156863, + 0.28627450980392155, 0.0039215686, 0.1803921569, 0.8235294118, 0.2901960784313726, + 0.0039215686, 0.1960784314, 0.8078431373, 0.29411764705882354, 0.0039215686, 0.2156862745, + 0.7921568627, 0.2980392156862745, 0.0039215686, 0.231372549, 0.7764705882, + 0.30196078431372547, 0.0039215686, 0.2509803922, 0.7607843137, 0.3058823529411765, + 0.0039215686, 0.262745098, 0.7490196078, 0.30980392156862746, 0.0039215686, 0.2823529412, + 0.7333333333, 0.3137254901960784, 0.0039215686, 0.2980392157, 0.7176470588, + 0.3176470588235294, 0.0039215686, 0.3176470588, 0.7019607843, 0.3215686274509804, + 0.0039215686, 0.3333333333, 0.6862745098, 0.3254901960784314, 0.0039215686, 0.3529411765, + 0.6705882353, 0.32941176470588235, 0.0039215686, 0.368627451, 0.6549019608, + 0.3333333333333333, 0.0039215686, 0.3882352941, 0.6392156863, 0.33725490196078434, + 0.0039215686, 0.4039215686, 0.6235294118, 0.3411764705882353, 0.0039215686, 0.4235294118, + 0.6078431373, 0.34509803921568627, 0.0039215686, 0.4392156863, 0.5921568627, + 0.34901960784313724, 0.0039215686, 0.4588235294, 0.5764705882, 0.35294117647058826, + 0.0039215686, 0.4745098039, 0.5607843137, 0.3568627450980392, 0.0039215686, 0.4941176471, + 0.5450980392, 0.3607843137254902, 0.0039215686, 0.5098039216, 0.5294117647, + 0.36470588235294116, 0.0039215686, 0.5294117647, 0.5137254902, 0.3686274509803922, + 0.0039215686, 0.5450980392, 0.4980392157, 0.37254901960784315, 0.0039215686, 0.5647058824, + 0.4784313725, 0.3764705882352941, 0.0039215686, 0.5803921569, 0.462745098, 0.3803921568627451, + 0.0039215686, 0.6, 0.4470588235, 0.3843137254901961, 0.0039215686, 0.6156862745, 0.4274509804, + 0.38823529411764707, 0.0039215686, 0.6352941176, 0.4117647059, 0.39215686274509803, + 0.0039215686, 0.6509803922, 0.3960784314, 0.396078431372549, 0.0039215686, 0.6705882353, + 0.3803921569, 0.4, 0.0039215686, 0.6862745098, 0.3607843137, 0.403921568627451, 0.0039215686, + 0.7058823529, 0.3450980392, 0.40784313725490196, 0.0039215686, 0.7215686275, 0.3294117647, + 0.4117647058823529, 0.0039215686, 0.7411764706, 0.3137254902, 0.41568627450980394, + 0.0039215686, 0.7529411765, 0.2941176471, 0.4196078431372549, 0.0039215686, 0.7960784314, + 0.2784313725, 0.4235294117647059, 0.0039215686, 0.7960784314, 0.262745098, + 0.42745098039215684, 0.0392156863, 0.8039215686, 0.2509803922, 0.43137254901960786, + 0.0745098039, 0.8117647059, 0.231372549, 0.43529411764705883, 0.1098039216, 0.8196078431, + 0.2156862745, 0.4392156862745098, 0.1450980392, 0.8274509804, 0.2, 0.44313725490196076, + 0.1803921569, 0.8352941176, 0.1843137255, 0.4470588235294118, 0.2156862745, 0.8431372549, + 0.1647058824, 0.45098039215686275, 0.2509803922, 0.8509803922, 0.1490196078, + 0.4549019607843137, 0.2823529412, 0.8588235294, 0.1333333333, 0.4588235294117647, + 0.3176470588, 0.8666666667, 0.1176470588, 0.4627450980392157, 0.3529411765, 0.8745098039, + 0.0980392157, 0.4666666666666667, 0.3882352941, 0.8823529412, 0.0823529412, + 0.4705882352941177, 0.4235294118, 0.8901960784, 0.0666666667, 0.4745098039215686, + 0.4588235294, 0.8980392157, 0.0509803922, 0.4784313725490197, 0.4941176471, 0.9058823529, + 0.0431372549, 0.48235294117647065, 0.5294117647, 0.9137254902, 0.031372549, + 0.48627450980392156, 0.5647058824, 0.9215686275, 0.0196078431, 0.49019607843137253, 0.6, + 0.9294117647, 0.0078431373, 0.49411764705882355, 0.6352941176, 0.937254902, 0.0039215686, + 0.4980392156862745, 0.6705882353, 0.9450980392, 0.0039215686, 0.5019607843137255, + 0.7058823529, 0.9490196078, 0.0039215686, 0.5058823529411764, 0.7411764706, 0.9568627451, + 0.0039215686, 0.5098039215686274, 0.7725490196, 0.9607843137, 0.0039215686, + 0.5137254901960784, 0.8078431373, 0.968627451, 0.0039215686, 0.5176470588235295, 0.8431372549, + 0.9725490196, 0.0039215686, 0.5215686274509804, 0.8784313725, 0.9803921569, 0.0039215686, + 0.5254901960784314, 0.9137254902, 0.9843137255, 0.0039215686, 0.5294117647058824, + 0.9490196078, 0.9921568627, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.9960784314, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.9960784314, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.9921568627, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.9843137255, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.9764705882, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.968627451, 0.0039215686, 0.5568627450980392, + 0.9960784314, 0.9607843137, 0.0039215686, 0.5607843137254902, 0.9960784314, 0.9529411765, + 0.0039215686, 0.5647058823529412, 0.9960784314, 0.9450980392, 0.0039215686, + 0.5686274509803921, 0.9960784314, 0.937254902, 0.0039215686, 0.5725490196078431, 0.9960784314, + 0.9294117647, 0.0039215686, 0.5764705882352941, 0.9960784314, 0.9215686275, 0.0039215686, + 0.5803921568627451, 0.9960784314, 0.9137254902, 0.0039215686, 0.5843137254901961, + 0.9960784314, 0.9058823529, 0.0039215686, 0.5882352941176471, 0.9960784314, 0.8980392157, + 0.0039215686, 0.592156862745098, 0.9960784314, 0.8901960784, 0.0039215686, 0.596078431372549, + 0.9960784314, 0.8823529412, 0.0039215686, 0.6, 0.9960784314, 0.8745098039, 0.0039215686, + 0.6039215686274509, 0.9960784314, 0.8666666667, 0.0039215686, 0.6078431372549019, + 0.9960784314, 0.8588235294, 0.0039215686, 0.611764705882353, 0.9960784314, 0.8509803922, + 0.0039215686, 0.615686274509804, 0.9960784314, 0.8431372549, 0.0039215686, 0.6196078431372549, + 0.9960784314, 0.8352941176, 0.0039215686, 0.6235294117647059, 0.9960784314, 0.8274509804, + 0.0039215686, 0.6274509803921569, 0.9960784314, 0.8196078431, 0.0039215686, + 0.6313725490196078, 0.9960784314, 0.8117647059, 0.0039215686, 0.6352941176470588, + 0.9960784314, 0.8039215686, 0.0039215686, 0.6392156862745098, 0.9960784314, 0.7960784314, + 0.0039215686, 0.6431372549019608, 0.9960784314, 0.7882352941, 0.0039215686, + 0.6470588235294118, 0.9960784314, 0.7803921569, 0.0039215686, 0.6509803921568628, + 0.9960784314, 0.7725490196, 0.0039215686, 0.6549019607843137, 0.9960784314, 0.7647058824, + 0.0039215686, 0.6588235294117647, 0.9960784314, 0.7568627451, 0.0039215686, + 0.6627450980392157, 0.9960784314, 0.7490196078, 0.0039215686, 0.6666666666666666, + 0.9960784314, 0.7450980392, 0.0039215686, 0.6705882352941176, 0.9960784314, 0.737254902, + 0.0039215686, 0.6745098039215687, 0.9960784314, 0.7294117647, 0.0039215686, + 0.6784313725490196, 0.9960784314, 0.7215686275, 0.0039215686, 0.6823529411764706, + 0.9960784314, 0.7137254902, 0.0039215686, 0.6862745098039216, 0.9960784314, 0.7058823529, + 0.0039215686, 0.6901960784313725, 0.9960784314, 0.6980392157, 0.0039215686, + 0.6941176470588235, 0.9960784314, 0.6901960784, 0.0039215686, 0.6980392156862745, + 0.9960784314, 0.6823529412, 0.0039215686, 0.7019607843137254, 0.9960784314, 0.6745098039, + 0.0039215686, 0.7058823529411765, 0.9960784314, 0.6666666667, 0.0039215686, + 0.7098039215686275, 0.9960784314, 0.6588235294, 0.0039215686, 0.7137254901960784, + 0.9960784314, 0.6509803922, 0.0039215686, 0.7176470588235294, 0.9960784314, 0.6431372549, + 0.0039215686, 0.7215686274509804, 0.9960784314, 0.6352941176, 0.0039215686, + 0.7254901960784313, 0.9960784314, 0.6274509804, 0.0039215686, 0.7294117647058823, + 0.9960784314, 0.6196078431, 0.0039215686, 0.7333333333333333, 0.9960784314, 0.6117647059, + 0.0039215686, 0.7372549019607844, 0.9960784314, 0.6039215686, 0.0039215686, + 0.7411764705882353, 0.9960784314, 0.5960784314, 0.0039215686, 0.7450980392156863, + 0.9960784314, 0.5882352941, 0.0039215686, 0.7490196078431373, 0.9960784314, 0.5803921569, + 0.0039215686, 0.7529411764705882, 0.9960784314, 0.5725490196, 0.0039215686, + 0.7568627450980392, 0.9960784314, 0.5647058824, 0.0039215686, 0.7607843137254902, + 0.9960784314, 0.5568627451, 0.0039215686, 0.7647058823529411, 0.9960784314, 0.5490196078, + 0.0039215686, 0.7686274509803922, 0.9960784314, 0.5411764706, 0.0039215686, + 0.7725490196078432, 0.9960784314, 0.5333333333, 0.0039215686, 0.7764705882352941, + 0.9960784314, 0.5254901961, 0.0039215686, 0.7803921568627451, 0.9960784314, 0.5176470588, + 0.0039215686, 0.7843137254901961, 0.9960784314, 0.5098039216, 0.0039215686, 0.788235294117647, + 0.9960784314, 0.5019607843, 0.0039215686, 0.792156862745098, 0.9960784314, 0.4941176471, + 0.0039215686, 0.796078431372549, 0.9960784314, 0.4862745098, 0.0039215686, 0.8, 0.9960784314, + 0.4784313725, 0.0039215686, 0.803921568627451, 0.9960784314, 0.4705882353, 0.0039215686, + 0.807843137254902, 0.9960784314, 0.462745098, 0.0039215686, 0.8117647058823529, 0.9960784314, + 0.4549019608, 0.0039215686, 0.8156862745098039, 0.9960784314, 0.4470588235, 0.0039215686, + 0.8196078431372549, 0.9960784314, 0.4392156863, 0.0039215686, 0.8235294117647058, + 0.9960784314, 0.431372549, 0.0039215686, 0.8274509803921568, 0.9960784314, 0.4235294118, + 0.0039215686, 0.8313725490196079, 0.9960784314, 0.4156862745, 0.0039215686, + 0.8352941176470589, 0.9960784314, 0.4078431373, 0.0039215686, 0.8392156862745098, + 0.9960784314, 0.4, 0.0039215686, 0.8431372549019608, 0.9960784314, 0.3921568627, 0.0039215686, + 0.8470588235294118, 0.9960784314, 0.3843137255, 0.0039215686, 0.8509803921568627, + 0.9960784314, 0.3764705882, 0.0039215686, 0.8549019607843137, 0.9960784314, 0.368627451, + 0.0039215686, 0.8588235294117647, 0.9960784314, 0.3607843137, 0.0039215686, + 0.8627450980392157, 0.9960784314, 0.3529411765, 0.0039215686, 0.8666666666666667, + 0.9960784314, 0.3450980392, 0.0039215686, 0.8705882352941177, 0.9960784314, 0.337254902, + 0.0039215686, 0.8745098039215686, 0.9960784314, 0.3294117647, 0.0039215686, + 0.8784313725490196, 0.9960784314, 0.3215686275, 0.0039215686, 0.8823529411764706, + 0.9960784314, 0.3137254902, 0.0039215686, 0.8862745098039215, 0.9960784314, 0.3058823529, + 0.0039215686, 0.8901960784313725, 0.9960784314, 0.2980392157, 0.0039215686, + 0.8941176470588236, 0.9960784314, 0.2901960784, 0.0039215686, 0.8980392156862745, + 0.9960784314, 0.2823529412, 0.0039215686, 0.9019607843137255, 0.9960784314, 0.2705882353, + 0.0039215686, 0.9058823529411765, 0.9960784314, 0.2588235294, 0.0039215686, + 0.9098039215686274, 0.9960784314, 0.2509803922, 0.0039215686, 0.9137254901960784, + 0.9960784314, 0.2431372549, 0.0039215686, 0.9176470588235294, 0.9960784314, 0.231372549, + 0.0039215686, 0.9215686274509803, 0.9960784314, 0.2196078431, 0.0039215686, + 0.9254901960784314, 0.9960784314, 0.2117647059, 0.0039215686, 0.9294117647058824, + 0.9960784314, 0.2, 0.0039215686, 0.9333333333333333, 0.9960784314, 0.1882352941, 0.0039215686, + 0.9372549019607843, 0.9960784314, 0.1764705882, 0.0039215686, 0.9411764705882354, + 0.9960784314, 0.168627451, 0.0039215686, 0.9450980392156864, 0.9960784314, 0.1568627451, + 0.0039215686, 0.9490196078431372, 0.9960784314, 0.1450980392, 0.0039215686, + 0.9529411764705882, 0.9960784314, 0.1333333333, 0.0039215686, 0.9568627450980394, + 0.9960784314, 0.1254901961, 0.0039215686, 0.9607843137254903, 0.9960784314, 0.1137254902, + 0.0039215686, 0.9647058823529413, 0.9960784314, 0.1019607843, 0.0039215686, + 0.9686274509803922, 0.9960784314, 0.0901960784, 0.0039215686, 0.9725490196078431, + 0.9960784314, 0.0823529412, 0.0039215686, 0.9764705882352941, 0.9960784314, 0.0705882353, + 0.0039215686, 0.9803921568627451, 0.9960784314, 0.0588235294, 0.0039215686, 0.984313725490196, + 0.9960784314, 0.0470588235, 0.0039215686, 0.9882352941176471, 0.9960784314, 0.0392156863, + 0.0039215686, 0.9921568627450981, 0.9960784314, 0.0274509804, 0.0039215686, 0.996078431372549, + 0.9960784314, 0.0156862745, 0.0039215686, 1.0, 0.9960784314, 0.0156862745, 0.0039215686, ], }, { ColorSpace: 'RGB', Name: 'perfusion', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0078431373, - 0.0235294118, - 0.0235294118, - 0.00784313725490196, - 0.0078431373, - 0.031372549, - 0.0470588235, - 0.011764705882352941, - 0.0078431373, - 0.0392156863, - 0.062745098, - 0.01568627450980392, - 0.0078431373, - 0.0470588235, - 0.0862745098, - 0.0196078431372549, - 0.0078431373, - 0.0549019608, - 0.1019607843, - 0.023529411764705882, - 0.0078431373, - 0.0549019608, - 0.1254901961, - 0.027450980392156862, - 0.0078431373, - 0.062745098, - 0.1411764706, - 0.03137254901960784, - 0.0078431373, - 0.0705882353, - 0.1647058824, - 0.03529411764705882, - 0.0078431373, - 0.0784313725, - 0.1803921569, - 0.0392156862745098, - 0.0078431373, - 0.0862745098, - 0.2039215686, - 0.043137254901960784, - 0.0078431373, - 0.0862745098, - 0.2196078431, - 0.047058823529411764, - 0.0078431373, - 0.0941176471, - 0.2431372549, - 0.050980392156862744, - 0.0078431373, - 0.1019607843, - 0.2666666667, - 0.054901960784313725, - 0.0078431373, - 0.1098039216, - 0.2823529412, - 0.05882352941176471, - 0.0078431373, - 0.1176470588, - 0.3058823529, - 0.06274509803921569, - 0.0078431373, - 0.1176470588, - 0.3215686275, - 0.06666666666666667, - 0.0078431373, - 0.1254901961, - 0.3450980392, - 0.07058823529411765, - 0.0078431373, - 0.1333333333, - 0.3607843137, - 0.07450980392156863, - 0.0078431373, - 0.1411764706, - 0.3843137255, - 0.0784313725490196, - 0.0078431373, - 0.1490196078, - 0.4, - 0.08235294117647059, - 0.0078431373, - 0.1490196078, - 0.4235294118, - 0.08627450980392157, - 0.0078431373, - 0.1568627451, - 0.4392156863, - 0.09019607843137255, - 0.0078431373, - 0.1647058824, - 0.462745098, - 0.09411764705882353, - 0.0078431373, - 0.1725490196, - 0.4784313725, - 0.09803921568627451, - 0.0078431373, - 0.1803921569, - 0.5019607843, - 0.10196078431372549, - 0.0078431373, - 0.1803921569, - 0.5254901961, - 0.10588235294117647, - 0.0078431373, - 0.1882352941, - 0.5411764706, - 0.10980392156862745, - 0.0078431373, - 0.1960784314, - 0.5647058824, - 0.11372549019607843, - 0.0078431373, - 0.2039215686, - 0.5803921569, - 0.11764705882352942, - 0.0078431373, - 0.2117647059, - 0.6039215686, - 0.12156862745098039, - 0.0078431373, - 0.2117647059, - 0.6196078431, - 0.12549019607843137, - 0.0078431373, - 0.2196078431, - 0.6431372549, - 0.12941176470588237, - 0.0078431373, - 0.2274509804, - 0.6588235294, - 0.13333333333333333, - 0.0078431373, - 0.2352941176, - 0.6823529412, - 0.13725490196078433, - 0.0078431373, - 0.2431372549, - 0.6980392157, - 0.1411764705882353, - 0.0078431373, - 0.2431372549, - 0.7215686275, - 0.1450980392156863, - 0.0078431373, - 0.2509803922, - 0.737254902, - 0.14901960784313725, - 0.0078431373, - 0.2588235294, - 0.7607843137, - 0.15294117647058825, - 0.0078431373, - 0.2666666667, - 0.7843137255, - 0.1568627450980392, - 0.0078431373, - 0.2745098039, - 0.8, - 0.1607843137254902, - 0.0078431373, - 0.2745098039, - 0.8235294118, - 0.16470588235294117, - 0.0078431373, - 0.2823529412, - 0.8392156863, - 0.16862745098039217, - 0.0078431373, - 0.2901960784, - 0.862745098, - 0.17254901960784313, - 0.0078431373, - 0.2980392157, - 0.8784313725, - 0.17647058823529413, - 0.0078431373, - 0.3058823529, - 0.9019607843, - 0.1803921568627451, - 0.0078431373, - 0.3058823529, - 0.9176470588, - 0.1843137254901961, - 0.0078431373, - 0.2980392157, - 0.9411764706, - 0.18823529411764706, - 0.0078431373, - 0.3058823529, - 0.9568627451, - 0.19215686274509805, - 0.0078431373, - 0.2980392157, - 0.9803921569, - 0.19607843137254902, - 0.0078431373, - 0.2980392157, - 0.9882352941, - 0.2, - 0.0078431373, - 0.2901960784, - 0.9803921569, - 0.20392156862745098, - 0.0078431373, - 0.2901960784, - 0.9647058824, - 0.20784313725490197, - 0.0078431373, - 0.2823529412, - 0.9568627451, - 0.21176470588235294, - 0.0078431373, - 0.2823529412, - 0.9411764706, - 0.21568627450980393, - 0.0078431373, - 0.2745098039, - 0.9333333333, - 0.2196078431372549, - 0.0078431373, - 0.2666666667, - 0.9176470588, - 0.2235294117647059, - 0.0078431373, - 0.2666666667, - 0.9098039216, - 0.22745098039215686, - 0.0078431373, - 0.2588235294, - 0.9019607843, - 0.23137254901960785, - 0.0078431373, - 0.2588235294, - 0.8862745098, - 0.23529411764705885, - 0.0078431373, - 0.2509803922, - 0.8784313725, - 0.23921568627450984, - 0.0078431373, - 0.2509803922, - 0.862745098, - 0.24313725490196078, - 0.0078431373, - 0.2431372549, - 0.8549019608, - 0.24705882352941178, - 0.0078431373, - 0.2352941176, - 0.8392156863, - 0.25098039215686274, - 0.0078431373, - 0.2352941176, - 0.831372549, - 0.2549019607843137, - 0.0078431373, - 0.2274509804, - 0.8235294118, - 0.25882352941176473, - 0.0078431373, - 0.2274509804, - 0.8078431373, - 0.2627450980392157, - 0.0078431373, - 0.2196078431, - 0.8, - 0.26666666666666666, - 0.0078431373, - 0.2196078431, - 0.7843137255, - 0.27058823529411763, - 0.0078431373, - 0.2117647059, - 0.7764705882, - 0.27450980392156865, - 0.0078431373, - 0.2039215686, - 0.7607843137, - 0.2784313725490196, - 0.0078431373, - 0.2039215686, - 0.7529411765, - 0.2823529411764706, - 0.0078431373, - 0.1960784314, - 0.7450980392, - 0.28627450980392155, - 0.0078431373, - 0.1960784314, - 0.7294117647, - 0.2901960784313726, - 0.0078431373, - 0.1882352941, - 0.7215686275, - 0.29411764705882354, - 0.0078431373, - 0.1882352941, - 0.7058823529, - 0.2980392156862745, - 0.0078431373, - 0.1803921569, - 0.6980392157, - 0.30196078431372547, - 0.0078431373, - 0.1803921569, - 0.6823529412, - 0.3058823529411765, - 0.0078431373, - 0.1725490196, - 0.6745098039, - 0.30980392156862746, - 0.0078431373, - 0.1647058824, - 0.6666666667, - 0.3137254901960784, - 0.0078431373, - 0.1647058824, - 0.6509803922, - 0.3176470588235294, - 0.0078431373, - 0.1568627451, - 0.6431372549, - 0.3215686274509804, - 0.0078431373, - 0.1568627451, - 0.6274509804, - 0.3254901960784314, - 0.0078431373, - 0.1490196078, - 0.6196078431, - 0.32941176470588235, - 0.0078431373, - 0.1490196078, - 0.6039215686, - 0.3333333333333333, - 0.0078431373, - 0.1411764706, - 0.5960784314, - 0.33725490196078434, - 0.0078431373, - 0.1333333333, - 0.5882352941, - 0.3411764705882353, - 0.0078431373, - 0.1333333333, - 0.5725490196, - 0.34509803921568627, - 0.0078431373, - 0.1254901961, - 0.5647058824, - 0.34901960784313724, - 0.0078431373, - 0.1254901961, - 0.5490196078, - 0.35294117647058826, - 0.0078431373, - 0.1176470588, - 0.5411764706, - 0.3568627450980392, - 0.0078431373, - 0.1176470588, - 0.5254901961, - 0.3607843137254902, - 0.0078431373, - 0.1098039216, - 0.5176470588, - 0.36470588235294116, - 0.0078431373, - 0.1019607843, - 0.5098039216, - 0.3686274509803922, - 0.0078431373, - 0.1019607843, - 0.4941176471, - 0.37254901960784315, - 0.0078431373, - 0.0941176471, - 0.4862745098, - 0.3764705882352941, - 0.0078431373, - 0.0941176471, - 0.4705882353, - 0.3803921568627451, - 0.0078431373, - 0.0862745098, - 0.462745098, - 0.3843137254901961, - 0.0078431373, - 0.0862745098, - 0.4470588235, - 0.38823529411764707, - 0.0078431373, - 0.0784313725, - 0.4392156863, - 0.39215686274509803, - 0.0078431373, - 0.0705882353, - 0.431372549, - 0.396078431372549, - 0.0078431373, - 0.0705882353, - 0.4156862745, - 0.4, - 0.0078431373, - 0.062745098, - 0.4078431373, - 0.403921568627451, - 0.0078431373, - 0.062745098, - 0.3921568627, - 0.40784313725490196, - 0.0078431373, - 0.0549019608, - 0.3843137255, - 0.4117647058823529, - 0.0078431373, - 0.0549019608, - 0.368627451, - 0.41568627450980394, - 0.0078431373, - 0.0470588235, - 0.3607843137, - 0.4196078431372549, - 0.0078431373, - 0.0470588235, - 0.3529411765, - 0.4235294117647059, - 0.0078431373, - 0.0392156863, - 0.337254902, - 0.42745098039215684, - 0.0078431373, - 0.031372549, - 0.3294117647, - 0.43137254901960786, - 0.0078431373, - 0.031372549, - 0.3137254902, - 0.43529411764705883, - 0.0078431373, - 0.0235294118, - 0.3058823529, - 0.4392156862745098, - 0.0078431373, - 0.0235294118, - 0.2901960784, - 0.44313725490196076, - 0.0078431373, - 0.0156862745, - 0.2823529412, - 0.4470588235294118, - 0.0078431373, - 0.0156862745, - 0.2745098039, - 0.45098039215686275, - 0.0078431373, - 0.0078431373, - 0.2588235294, - 0.4549019607843137, - 0.0235294118, - 0.0078431373, - 0.2509803922, - 0.4588235294117647, - 0.0078431373, - 0.0078431373, - 0.2352941176, - 0.4627450980392157, - 0.0078431373, - 0.0078431373, - 0.2274509804, - 0.4666666666666667, - 0.0078431373, - 0.0078431373, - 0.2117647059, - 0.4705882352941177, - 0.0078431373, - 0.0078431373, - 0.2039215686, - 0.4745098039215686, - 0.0078431373, - 0.0078431373, - 0.1960784314, - 0.4784313725490197, - 0.0078431373, - 0.0078431373, - 0.1803921569, - 0.48235294117647065, - 0.0078431373, - 0.0078431373, - 0.1725490196, - 0.48627450980392156, - 0.0078431373, - 0.0078431373, - 0.1568627451, - 0.49019607843137253, - 0.0078431373, - 0.0078431373, - 0.1490196078, - 0.49411764705882355, - 0.0078431373, - 0.0078431373, - 0.1333333333, - 0.4980392156862745, - 0.0078431373, - 0.0078431373, - 0.1254901961, - 0.5019607843137255, - 0.0078431373, - 0.0078431373, - 0.1176470588, - 0.5058823529411764, - 0.0078431373, - 0.0078431373, - 0.1019607843, - 0.5098039215686274, - 0.0078431373, - 0.0078431373, - 0.0941176471, - 0.5137254901960784, - 0.0078431373, - 0.0078431373, - 0.0784313725, - 0.5176470588235295, - 0.0078431373, - 0.0078431373, - 0.0705882353, - 0.5215686274509804, - 0.0078431373, - 0.0078431373, - 0.0549019608, - 0.5254901960784314, - 0.0078431373, - 0.0078431373, - 0.0470588235, - 0.5294117647058824, - 0.0235294118, - 0.0078431373, - 0.0392156863, - 0.5333333333333333, - 0.031372549, - 0.0078431373, - 0.0235294118, - 0.5372549019607843, - 0.0392156863, - 0.0078431373, - 0.0156862745, - 0.5411764705882353, - 0.0549019608, - 0.0078431373, - 0.0, - 0.5450980392156862, - 0.062745098, - 0.0078431373, - 0.0, - 0.5490196078431373, - 0.0705882353, - 0.0078431373, - 0.0, - 0.5529411764705883, - 0.0862745098, - 0.0078431373, - 0.0, - 0.5568627450980392, - 0.0941176471, - 0.0078431373, - 0.0, - 0.5607843137254902, - 0.1019607843, - 0.0078431373, - 0.0, - 0.5647058823529412, - 0.1098039216, - 0.0078431373, - 0.0, - 0.5686274509803921, - 0.1254901961, - 0.0078431373, - 0.0, - 0.5725490196078431, - 0.1333333333, - 0.0078431373, - 0.0, - 0.5764705882352941, - 0.1411764706, - 0.0078431373, - 0.0, - 0.5803921568627451, - 0.1568627451, - 0.0078431373, - 0.0, - 0.5843137254901961, - 0.1647058824, - 0.0078431373, - 0.0, - 0.5882352941176471, - 0.1725490196, - 0.0078431373, - 0.0, - 0.592156862745098, - 0.1882352941, - 0.0078431373, - 0.0, - 0.596078431372549, - 0.1960784314, - 0.0078431373, - 0.0, - 0.6, - 0.2039215686, - 0.0078431373, - 0.0, - 0.6039215686274509, - 0.2117647059, - 0.0078431373, - 0.0, - 0.6078431372549019, - 0.2274509804, - 0.0078431373, - 0.0, - 0.611764705882353, - 0.2352941176, - 0.0078431373, - 0.0, - 0.615686274509804, - 0.2431372549, - 0.0078431373, - 0.0, - 0.6196078431372549, - 0.2588235294, - 0.0078431373, - 0.0, - 0.6235294117647059, - 0.2666666667, - 0.0078431373, - 0.0, - 0.6274509803921569, - 0.2745098039, - 0.0, - 0.0, - 0.6313725490196078, - 0.2901960784, - 0.0156862745, - 0.0, - 0.6352941176470588, - 0.2980392157, - 0.0235294118, - 0.0, - 0.6392156862745098, - 0.3058823529, - 0.0392156863, - 0.0, - 0.6431372549019608, - 0.3137254902, - 0.0470588235, - 0.0, - 0.6470588235294118, - 0.3294117647, - 0.0549019608, - 0.0, - 0.6509803921568628, - 0.337254902, - 0.0705882353, - 0.0, - 0.6549019607843137, - 0.3450980392, - 0.0784313725, - 0.0, - 0.6588235294117647, - 0.3607843137, - 0.0862745098, - 0.0, - 0.6627450980392157, - 0.368627451, - 0.1019607843, - 0.0, - 0.6666666666666666, - 0.3764705882, - 0.1098039216, - 0.0, - 0.6705882352941176, - 0.3843137255, - 0.1176470588, - 0.0, - 0.6745098039215687, - 0.4, - 0.1333333333, - 0.0, - 0.6784313725490196, - 0.4078431373, - 0.1411764706, - 0.0, - 0.6823529411764706, - 0.4156862745, - 0.1490196078, - 0.0, - 0.6862745098039216, - 0.431372549, - 0.1647058824, - 0.0, - 0.6901960784313725, - 0.4392156863, - 0.1725490196, - 0.0, - 0.6941176470588235, - 0.4470588235, - 0.1803921569, - 0.0, - 0.6980392156862745, - 0.462745098, - 0.1960784314, - 0.0, - 0.7019607843137254, - 0.4705882353, - 0.2039215686, - 0.0, - 0.7058823529411765, - 0.4784313725, - 0.2117647059, - 0.0, - 0.7098039215686275, - 0.4862745098, - 0.2274509804, - 0.0, - 0.7137254901960784, - 0.5019607843, - 0.2352941176, - 0.0, - 0.7176470588235294, - 0.5098039216, - 0.2431372549, - 0.0, - 0.7215686274509804, - 0.5176470588, - 0.2588235294, - 0.0, - 0.7254901960784313, - 0.5333333333, - 0.2666666667, - 0.0, - 0.7294117647058823, - 0.5411764706, - 0.2745098039, - 0.0, - 0.7333333333333333, - 0.5490196078, - 0.2901960784, - 0.0, - 0.7372549019607844, - 0.5647058824, - 0.2980392157, - 0.0, - 0.7411764705882353, - 0.5725490196, - 0.3058823529, - 0.0, - 0.7450980392156863, - 0.5803921569, - 0.3215686275, - 0.0, - 0.7490196078431373, - 0.5882352941, - 0.3294117647, - 0.0, - 0.7529411764705882, - 0.6039215686, - 0.337254902, - 0.0, - 0.7568627450980392, - 0.6117647059, - 0.3529411765, - 0.0, - 0.7607843137254902, - 0.6196078431, - 0.3607843137, - 0.0, - 0.7647058823529411, - 0.6352941176, - 0.368627451, - 0.0, - 0.7686274509803922, - 0.6431372549, - 0.3843137255, - 0.0, - 0.7725490196078432, - 0.6509803922, - 0.3921568627, - 0.0, - 0.7764705882352941, - 0.6588235294, - 0.4, - 0.0, - 0.7803921568627451, - 0.6745098039, - 0.4156862745, - 0.0, - 0.7843137254901961, - 0.6823529412, - 0.4235294118, - 0.0, - 0.788235294117647, - 0.6901960784, - 0.431372549, - 0.0, - 0.792156862745098, - 0.7058823529, - 0.4470588235, - 0.0, - 0.796078431372549, - 0.7137254902, - 0.4549019608, - 0.0, - 0.8, - 0.7215686275, - 0.462745098, - 0.0, - 0.803921568627451, - 0.737254902, - 0.4784313725, - 0.0, - 0.807843137254902, - 0.7450980392, - 0.4862745098, - 0.0, - 0.8117647058823529, - 0.7529411765, - 0.4941176471, - 0.0, - 0.8156862745098039, - 0.7607843137, - 0.5098039216, - 0.0, - 0.8196078431372549, - 0.7764705882, - 0.5176470588, - 0.0, - 0.8235294117647058, - 0.7843137255, - 0.5254901961, - 0.0, - 0.8274509803921568, - 0.7921568627, - 0.5411764706, - 0.0, - 0.8313725490196079, - 0.8078431373, - 0.5490196078, - 0.0, - 0.8352941176470589, - 0.8156862745, - 0.5568627451, - 0.0, - 0.8392156862745098, - 0.8235294118, - 0.5725490196, - 0.0, - 0.8431372549019608, - 0.8392156863, - 0.5803921569, - 0.0, - 0.8470588235294118, - 0.8470588235, - 0.5882352941, - 0.0, - 0.8509803921568627, - 0.8549019608, - 0.6039215686, - 0.0, - 0.8549019607843137, - 0.862745098, - 0.6117647059, - 0.0, - 0.8588235294117647, - 0.8784313725, - 0.6196078431, - 0.0, - 0.8627450980392157, - 0.8862745098, - 0.6352941176, - 0.0, - 0.8666666666666667, - 0.8941176471, - 0.6431372549, - 0.0, - 0.8705882352941177, - 0.9098039216, - 0.6509803922, - 0.0, - 0.8745098039215686, - 0.9176470588, - 0.6666666667, - 0.0, - 0.8784313725490196, - 0.9254901961, - 0.6745098039, - 0.0, - 0.8823529411764706, - 0.9411764706, - 0.6823529412, - 0.0, - 0.8862745098039215, - 0.9490196078, - 0.6980392157, - 0.0, - 0.8901960784313725, - 0.9568627451, - 0.7058823529, - 0.0, - 0.8941176470588236, - 0.9647058824, - 0.7137254902, - 0.0, - 0.8980392156862745, - 0.9803921569, - 0.7294117647, - 0.0, - 0.9019607843137255, - 0.9882352941, - 0.737254902, - 0.0, - 0.9058823529411765, - 0.9960784314, - 0.7450980392, - 0.0, - 0.9098039215686274, - 0.9960784314, - 0.7607843137, - 0.0, - 0.9137254901960784, - 0.9960784314, - 0.768627451, - 0.0, - 0.9176470588235294, - 0.9960784314, - 0.7764705882, - 0.0, - 0.9215686274509803, - 0.9960784314, - 0.7921568627, - 0.0, - 0.9254901960784314, - 0.9960784314, - 0.8, - 0.0, - 0.9294117647058824, - 0.9960784314, - 0.8078431373, - 0.0, - 0.9333333333333333, - 0.9960784314, - 0.8235294118, - 0.0, - 0.9372549019607843, - 0.9960784314, - 0.831372549, - 0.0, - 0.9411764705882354, - 0.9960784314, - 0.8392156863, - 0.0, - 0.9450980392156864, - 0.9960784314, - 0.8549019608, - 0.0, - 0.9490196078431372, - 0.9960784314, - 0.862745098, - 0.0549019608, - 0.9529411764705882, - 0.9960784314, - 0.8705882353, - 0.1098039216, - 0.9568627450980394, - 0.9960784314, - 0.8862745098, - 0.1647058824, - 0.9607843137254903, - 0.9960784314, - 0.8941176471, - 0.2196078431, - 0.9647058823529413, - 0.9960784314, - 0.9019607843, - 0.2666666667, - 0.9686274509803922, - 0.9960784314, - 0.9176470588, - 0.3215686275, - 0.9725490196078431, - 0.9960784314, - 0.9254901961, - 0.3764705882, - 0.9764705882352941, - 0.9960784314, - 0.9333333333, - 0.431372549, - 0.9803921568627451, - 0.9960784314, - 0.9490196078, - 0.4862745098, - 0.984313725490196, - 0.9960784314, - 0.9568627451, - 0.5333333333, - 0.9882352941176471, - 0.9960784314, - 0.9647058824, - 0.5882352941, - 0.9921568627450981, - 0.9960784314, - 0.9803921569, - 0.6431372549, - 0.996078431372549, - 0.9960784314, - 0.9882352941, - 0.6980392157, - 1.0, - 0.9960784314, - 0.9960784314, - 0.7450980392, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0078431373, 0.0235294118, 0.0235294118, + 0.00784313725490196, 0.0078431373, 0.031372549, 0.0470588235, 0.011764705882352941, + 0.0078431373, 0.0392156863, 0.062745098, 0.01568627450980392, 0.0078431373, 0.0470588235, + 0.0862745098, 0.0196078431372549, 0.0078431373, 0.0549019608, 0.1019607843, + 0.023529411764705882, 0.0078431373, 0.0549019608, 0.1254901961, 0.027450980392156862, + 0.0078431373, 0.062745098, 0.1411764706, 0.03137254901960784, 0.0078431373, 0.0705882353, + 0.1647058824, 0.03529411764705882, 0.0078431373, 0.0784313725, 0.1803921569, + 0.0392156862745098, 0.0078431373, 0.0862745098, 0.2039215686, 0.043137254901960784, + 0.0078431373, 0.0862745098, 0.2196078431, 0.047058823529411764, 0.0078431373, 0.0941176471, + 0.2431372549, 0.050980392156862744, 0.0078431373, 0.1019607843, 0.2666666667, + 0.054901960784313725, 0.0078431373, 0.1098039216, 0.2823529412, 0.05882352941176471, + 0.0078431373, 0.1176470588, 0.3058823529, 0.06274509803921569, 0.0078431373, 0.1176470588, + 0.3215686275, 0.06666666666666667, 0.0078431373, 0.1254901961, 0.3450980392, + 0.07058823529411765, 0.0078431373, 0.1333333333, 0.3607843137, 0.07450980392156863, + 0.0078431373, 0.1411764706, 0.3843137255, 0.0784313725490196, 0.0078431373, 0.1490196078, 0.4, + 0.08235294117647059, 0.0078431373, 0.1490196078, 0.4235294118, 0.08627450980392157, + 0.0078431373, 0.1568627451, 0.4392156863, 0.09019607843137255, 0.0078431373, 0.1647058824, + 0.462745098, 0.09411764705882353, 0.0078431373, 0.1725490196, 0.4784313725, + 0.09803921568627451, 0.0078431373, 0.1803921569, 0.5019607843, 0.10196078431372549, + 0.0078431373, 0.1803921569, 0.5254901961, 0.10588235294117647, 0.0078431373, 0.1882352941, + 0.5411764706, 0.10980392156862745, 0.0078431373, 0.1960784314, 0.5647058824, + 0.11372549019607843, 0.0078431373, 0.2039215686, 0.5803921569, 0.11764705882352942, + 0.0078431373, 0.2117647059, 0.6039215686, 0.12156862745098039, 0.0078431373, 0.2117647059, + 0.6196078431, 0.12549019607843137, 0.0078431373, 0.2196078431, 0.6431372549, + 0.12941176470588237, 0.0078431373, 0.2274509804, 0.6588235294, 0.13333333333333333, + 0.0078431373, 0.2352941176, 0.6823529412, 0.13725490196078433, 0.0078431373, 0.2431372549, + 0.6980392157, 0.1411764705882353, 0.0078431373, 0.2431372549, 0.7215686275, + 0.1450980392156863, 0.0078431373, 0.2509803922, 0.737254902, 0.14901960784313725, + 0.0078431373, 0.2588235294, 0.7607843137, 0.15294117647058825, 0.0078431373, 0.2666666667, + 0.7843137255, 0.1568627450980392, 0.0078431373, 0.2745098039, 0.8, 0.1607843137254902, + 0.0078431373, 0.2745098039, 0.8235294118, 0.16470588235294117, 0.0078431373, 0.2823529412, + 0.8392156863, 0.16862745098039217, 0.0078431373, 0.2901960784, 0.862745098, + 0.17254901960784313, 0.0078431373, 0.2980392157, 0.8784313725, 0.17647058823529413, + 0.0078431373, 0.3058823529, 0.9019607843, 0.1803921568627451, 0.0078431373, 0.3058823529, + 0.9176470588, 0.1843137254901961, 0.0078431373, 0.2980392157, 0.9411764706, + 0.18823529411764706, 0.0078431373, 0.3058823529, 0.9568627451, 0.19215686274509805, + 0.0078431373, 0.2980392157, 0.9803921569, 0.19607843137254902, 0.0078431373, 0.2980392157, + 0.9882352941, 0.2, 0.0078431373, 0.2901960784, 0.9803921569, 0.20392156862745098, + 0.0078431373, 0.2901960784, 0.9647058824, 0.20784313725490197, 0.0078431373, 0.2823529412, + 0.9568627451, 0.21176470588235294, 0.0078431373, 0.2823529412, 0.9411764706, + 0.21568627450980393, 0.0078431373, 0.2745098039, 0.9333333333, 0.2196078431372549, + 0.0078431373, 0.2666666667, 0.9176470588, 0.2235294117647059, 0.0078431373, 0.2666666667, + 0.9098039216, 0.22745098039215686, 0.0078431373, 0.2588235294, 0.9019607843, + 0.23137254901960785, 0.0078431373, 0.2588235294, 0.8862745098, 0.23529411764705885, + 0.0078431373, 0.2509803922, 0.8784313725, 0.23921568627450984, 0.0078431373, 0.2509803922, + 0.862745098, 0.24313725490196078, 0.0078431373, 0.2431372549, 0.8549019608, + 0.24705882352941178, 0.0078431373, 0.2352941176, 0.8392156863, 0.25098039215686274, + 0.0078431373, 0.2352941176, 0.831372549, 0.2549019607843137, 0.0078431373, 0.2274509804, + 0.8235294118, 0.25882352941176473, 0.0078431373, 0.2274509804, 0.8078431373, + 0.2627450980392157, 0.0078431373, 0.2196078431, 0.8, 0.26666666666666666, 0.0078431373, + 0.2196078431, 0.7843137255, 0.27058823529411763, 0.0078431373, 0.2117647059, 0.7764705882, + 0.27450980392156865, 0.0078431373, 0.2039215686, 0.7607843137, 0.2784313725490196, + 0.0078431373, 0.2039215686, 0.7529411765, 0.2823529411764706, 0.0078431373, 0.1960784314, + 0.7450980392, 0.28627450980392155, 0.0078431373, 0.1960784314, 0.7294117647, + 0.2901960784313726, 0.0078431373, 0.1882352941, 0.7215686275, 0.29411764705882354, + 0.0078431373, 0.1882352941, 0.7058823529, 0.2980392156862745, 0.0078431373, 0.1803921569, + 0.6980392157, 0.30196078431372547, 0.0078431373, 0.1803921569, 0.6823529412, + 0.3058823529411765, 0.0078431373, 0.1725490196, 0.6745098039, 0.30980392156862746, + 0.0078431373, 0.1647058824, 0.6666666667, 0.3137254901960784, 0.0078431373, 0.1647058824, + 0.6509803922, 0.3176470588235294, 0.0078431373, 0.1568627451, 0.6431372549, + 0.3215686274509804, 0.0078431373, 0.1568627451, 0.6274509804, 0.3254901960784314, + 0.0078431373, 0.1490196078, 0.6196078431, 0.32941176470588235, 0.0078431373, 0.1490196078, + 0.6039215686, 0.3333333333333333, 0.0078431373, 0.1411764706, 0.5960784314, + 0.33725490196078434, 0.0078431373, 0.1333333333, 0.5882352941, 0.3411764705882353, + 0.0078431373, 0.1333333333, 0.5725490196, 0.34509803921568627, 0.0078431373, 0.1254901961, + 0.5647058824, 0.34901960784313724, 0.0078431373, 0.1254901961, 0.5490196078, + 0.35294117647058826, 0.0078431373, 0.1176470588, 0.5411764706, 0.3568627450980392, + 0.0078431373, 0.1176470588, 0.5254901961, 0.3607843137254902, 0.0078431373, 0.1098039216, + 0.5176470588, 0.36470588235294116, 0.0078431373, 0.1019607843, 0.5098039216, + 0.3686274509803922, 0.0078431373, 0.1019607843, 0.4941176471, 0.37254901960784315, + 0.0078431373, 0.0941176471, 0.4862745098, 0.3764705882352941, 0.0078431373, 0.0941176471, + 0.4705882353, 0.3803921568627451, 0.0078431373, 0.0862745098, 0.462745098, 0.3843137254901961, + 0.0078431373, 0.0862745098, 0.4470588235, 0.38823529411764707, 0.0078431373, 0.0784313725, + 0.4392156863, 0.39215686274509803, 0.0078431373, 0.0705882353, 0.431372549, 0.396078431372549, + 0.0078431373, 0.0705882353, 0.4156862745, 0.4, 0.0078431373, 0.062745098, 0.4078431373, + 0.403921568627451, 0.0078431373, 0.062745098, 0.3921568627, 0.40784313725490196, 0.0078431373, + 0.0549019608, 0.3843137255, 0.4117647058823529, 0.0078431373, 0.0549019608, 0.368627451, + 0.41568627450980394, 0.0078431373, 0.0470588235, 0.3607843137, 0.4196078431372549, + 0.0078431373, 0.0470588235, 0.3529411765, 0.4235294117647059, 0.0078431373, 0.0392156863, + 0.337254902, 0.42745098039215684, 0.0078431373, 0.031372549, 0.3294117647, + 0.43137254901960786, 0.0078431373, 0.031372549, 0.3137254902, 0.43529411764705883, + 0.0078431373, 0.0235294118, 0.3058823529, 0.4392156862745098, 0.0078431373, 0.0235294118, + 0.2901960784, 0.44313725490196076, 0.0078431373, 0.0156862745, 0.2823529412, + 0.4470588235294118, 0.0078431373, 0.0156862745, 0.2745098039, 0.45098039215686275, + 0.0078431373, 0.0078431373, 0.2588235294, 0.4549019607843137, 0.0235294118, 0.0078431373, + 0.2509803922, 0.4588235294117647, 0.0078431373, 0.0078431373, 0.2352941176, + 0.4627450980392157, 0.0078431373, 0.0078431373, 0.2274509804, 0.4666666666666667, + 0.0078431373, 0.0078431373, 0.2117647059, 0.4705882352941177, 0.0078431373, 0.0078431373, + 0.2039215686, 0.4745098039215686, 0.0078431373, 0.0078431373, 0.1960784314, + 0.4784313725490197, 0.0078431373, 0.0078431373, 0.1803921569, 0.48235294117647065, + 0.0078431373, 0.0078431373, 0.1725490196, 0.48627450980392156, 0.0078431373, 0.0078431373, + 0.1568627451, 0.49019607843137253, 0.0078431373, 0.0078431373, 0.1490196078, + 0.49411764705882355, 0.0078431373, 0.0078431373, 0.1333333333, 0.4980392156862745, + 0.0078431373, 0.0078431373, 0.1254901961, 0.5019607843137255, 0.0078431373, 0.0078431373, + 0.1176470588, 0.5058823529411764, 0.0078431373, 0.0078431373, 0.1019607843, + 0.5098039215686274, 0.0078431373, 0.0078431373, 0.0941176471, 0.5137254901960784, + 0.0078431373, 0.0078431373, 0.0784313725, 0.5176470588235295, 0.0078431373, 0.0078431373, + 0.0705882353, 0.5215686274509804, 0.0078431373, 0.0078431373, 0.0549019608, + 0.5254901960784314, 0.0078431373, 0.0078431373, 0.0470588235, 0.5294117647058824, + 0.0235294118, 0.0078431373, 0.0392156863, 0.5333333333333333, 0.031372549, 0.0078431373, + 0.0235294118, 0.5372549019607843, 0.0392156863, 0.0078431373, 0.0156862745, + 0.5411764705882353, 0.0549019608, 0.0078431373, 0.0, 0.5450980392156862, 0.062745098, + 0.0078431373, 0.0, 0.5490196078431373, 0.0705882353, 0.0078431373, 0.0, 0.5529411764705883, + 0.0862745098, 0.0078431373, 0.0, 0.5568627450980392, 0.0941176471, 0.0078431373, 0.0, + 0.5607843137254902, 0.1019607843, 0.0078431373, 0.0, 0.5647058823529412, 0.1098039216, + 0.0078431373, 0.0, 0.5686274509803921, 0.1254901961, 0.0078431373, 0.0, 0.5725490196078431, + 0.1333333333, 0.0078431373, 0.0, 0.5764705882352941, 0.1411764706, 0.0078431373, 0.0, + 0.5803921568627451, 0.1568627451, 0.0078431373, 0.0, 0.5843137254901961, 0.1647058824, + 0.0078431373, 0.0, 0.5882352941176471, 0.1725490196, 0.0078431373, 0.0, 0.592156862745098, + 0.1882352941, 0.0078431373, 0.0, 0.596078431372549, 0.1960784314, 0.0078431373, 0.0, 0.6, + 0.2039215686, 0.0078431373, 0.0, 0.6039215686274509, 0.2117647059, 0.0078431373, 0.0, + 0.6078431372549019, 0.2274509804, 0.0078431373, 0.0, 0.611764705882353, 0.2352941176, + 0.0078431373, 0.0, 0.615686274509804, 0.2431372549, 0.0078431373, 0.0, 0.6196078431372549, + 0.2588235294, 0.0078431373, 0.0, 0.6235294117647059, 0.2666666667, 0.0078431373, 0.0, + 0.6274509803921569, 0.2745098039, 0.0, 0.0, 0.6313725490196078, 0.2901960784, 0.0156862745, + 0.0, 0.6352941176470588, 0.2980392157, 0.0235294118, 0.0, 0.6392156862745098, 0.3058823529, + 0.0392156863, 0.0, 0.6431372549019608, 0.3137254902, 0.0470588235, 0.0, 0.6470588235294118, + 0.3294117647, 0.0549019608, 0.0, 0.6509803921568628, 0.337254902, 0.0705882353, 0.0, + 0.6549019607843137, 0.3450980392, 0.0784313725, 0.0, 0.6588235294117647, 0.3607843137, + 0.0862745098, 0.0, 0.6627450980392157, 0.368627451, 0.1019607843, 0.0, 0.6666666666666666, + 0.3764705882, 0.1098039216, 0.0, 0.6705882352941176, 0.3843137255, 0.1176470588, 0.0, + 0.6745098039215687, 0.4, 0.1333333333, 0.0, 0.6784313725490196, 0.4078431373, 0.1411764706, + 0.0, 0.6823529411764706, 0.4156862745, 0.1490196078, 0.0, 0.6862745098039216, 0.431372549, + 0.1647058824, 0.0, 0.6901960784313725, 0.4392156863, 0.1725490196, 0.0, 0.6941176470588235, + 0.4470588235, 0.1803921569, 0.0, 0.6980392156862745, 0.462745098, 0.1960784314, 0.0, + 0.7019607843137254, 0.4705882353, 0.2039215686, 0.0, 0.7058823529411765, 0.4784313725, + 0.2117647059, 0.0, 0.7098039215686275, 0.4862745098, 0.2274509804, 0.0, 0.7137254901960784, + 0.5019607843, 0.2352941176, 0.0, 0.7176470588235294, 0.5098039216, 0.2431372549, 0.0, + 0.7215686274509804, 0.5176470588, 0.2588235294, 0.0, 0.7254901960784313, 0.5333333333, + 0.2666666667, 0.0, 0.7294117647058823, 0.5411764706, 0.2745098039, 0.0, 0.7333333333333333, + 0.5490196078, 0.2901960784, 0.0, 0.7372549019607844, 0.5647058824, 0.2980392157, 0.0, + 0.7411764705882353, 0.5725490196, 0.3058823529, 0.0, 0.7450980392156863, 0.5803921569, + 0.3215686275, 0.0, 0.7490196078431373, 0.5882352941, 0.3294117647, 0.0, 0.7529411764705882, + 0.6039215686, 0.337254902, 0.0, 0.7568627450980392, 0.6117647059, 0.3529411765, 0.0, + 0.7607843137254902, 0.6196078431, 0.3607843137, 0.0, 0.7647058823529411, 0.6352941176, + 0.368627451, 0.0, 0.7686274509803922, 0.6431372549, 0.3843137255, 0.0, 0.7725490196078432, + 0.6509803922, 0.3921568627, 0.0, 0.7764705882352941, 0.6588235294, 0.4, 0.0, + 0.7803921568627451, 0.6745098039, 0.4156862745, 0.0, 0.7843137254901961, 0.6823529412, + 0.4235294118, 0.0, 0.788235294117647, 0.6901960784, 0.431372549, 0.0, 0.792156862745098, + 0.7058823529, 0.4470588235, 0.0, 0.796078431372549, 0.7137254902, 0.4549019608, 0.0, 0.8, + 0.7215686275, 0.462745098, 0.0, 0.803921568627451, 0.737254902, 0.4784313725, 0.0, + 0.807843137254902, 0.7450980392, 0.4862745098, 0.0, 0.8117647058823529, 0.7529411765, + 0.4941176471, 0.0, 0.8156862745098039, 0.7607843137, 0.5098039216, 0.0, 0.8196078431372549, + 0.7764705882, 0.5176470588, 0.0, 0.8235294117647058, 0.7843137255, 0.5254901961, 0.0, + 0.8274509803921568, 0.7921568627, 0.5411764706, 0.0, 0.8313725490196079, 0.8078431373, + 0.5490196078, 0.0, 0.8352941176470589, 0.8156862745, 0.5568627451, 0.0, 0.8392156862745098, + 0.8235294118, 0.5725490196, 0.0, 0.8431372549019608, 0.8392156863, 0.5803921569, 0.0, + 0.8470588235294118, 0.8470588235, 0.5882352941, 0.0, 0.8509803921568627, 0.8549019608, + 0.6039215686, 0.0, 0.8549019607843137, 0.862745098, 0.6117647059, 0.0, 0.8588235294117647, + 0.8784313725, 0.6196078431, 0.0, 0.8627450980392157, 0.8862745098, 0.6352941176, 0.0, + 0.8666666666666667, 0.8941176471, 0.6431372549, 0.0, 0.8705882352941177, 0.9098039216, + 0.6509803922, 0.0, 0.8745098039215686, 0.9176470588, 0.6666666667, 0.0, 0.8784313725490196, + 0.9254901961, 0.6745098039, 0.0, 0.8823529411764706, 0.9411764706, 0.6823529412, 0.0, + 0.8862745098039215, 0.9490196078, 0.6980392157, 0.0, 0.8901960784313725, 0.9568627451, + 0.7058823529, 0.0, 0.8941176470588236, 0.9647058824, 0.7137254902, 0.0, 0.8980392156862745, + 0.9803921569, 0.7294117647, 0.0, 0.9019607843137255, 0.9882352941, 0.737254902, 0.0, + 0.9058823529411765, 0.9960784314, 0.7450980392, 0.0, 0.9098039215686274, 0.9960784314, + 0.7607843137, 0.0, 0.9137254901960784, 0.9960784314, 0.768627451, 0.0, 0.9176470588235294, + 0.9960784314, 0.7764705882, 0.0, 0.9215686274509803, 0.9960784314, 0.7921568627, 0.0, + 0.9254901960784314, 0.9960784314, 0.8, 0.0, 0.9294117647058824, 0.9960784314, 0.8078431373, + 0.0, 0.9333333333333333, 0.9960784314, 0.8235294118, 0.0, 0.9372549019607843, 0.9960784314, + 0.831372549, 0.0, 0.9411764705882354, 0.9960784314, 0.8392156863, 0.0, 0.9450980392156864, + 0.9960784314, 0.8549019608, 0.0, 0.9490196078431372, 0.9960784314, 0.862745098, 0.0549019608, + 0.9529411764705882, 0.9960784314, 0.8705882353, 0.1098039216, 0.9568627450980394, + 0.9960784314, 0.8862745098, 0.1647058824, 0.9607843137254903, 0.9960784314, 0.8941176471, + 0.2196078431, 0.9647058823529413, 0.9960784314, 0.9019607843, 0.2666666667, + 0.9686274509803922, 0.9960784314, 0.9176470588, 0.3215686275, 0.9725490196078431, + 0.9960784314, 0.9254901961, 0.3764705882, 0.9764705882352941, 0.9960784314, 0.9333333333, + 0.431372549, 0.9803921568627451, 0.9960784314, 0.9490196078, 0.4862745098, 0.984313725490196, + 0.9960784314, 0.9568627451, 0.5333333333, 0.9882352941176471, 0.9960784314, 0.9647058824, + 0.5882352941, 0.9921568627450981, 0.9960784314, 0.9803921569, 0.6431372549, 0.996078431372549, + 0.9960784314, 0.9882352941, 0.6980392157, 1.0, 0.9960784314, 0.9960784314, 0.7450980392, ], }, { ColorSpace: 'RGB', Name: 'rainbow_2', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0156862745, - 0.0, - 0.0117647059, - 0.00784313725490196, - 0.0352941176, - 0.0, - 0.0274509804, - 0.011764705882352941, - 0.0509803922, - 0.0, - 0.0392156863, - 0.01568627450980392, - 0.0705882353, - 0.0, - 0.0549019608, - 0.0196078431372549, - 0.0862745098, - 0.0, - 0.0745098039, - 0.023529411764705882, - 0.1058823529, - 0.0, - 0.0901960784, - 0.027450980392156862, - 0.1215686275, - 0.0, - 0.1098039216, - 0.03137254901960784, - 0.1411764706, - 0.0, - 0.1254901961, - 0.03529411764705882, - 0.1568627451, - 0.0, - 0.1490196078, - 0.0392156862745098, - 0.1764705882, - 0.0, - 0.168627451, - 0.043137254901960784, - 0.1960784314, - 0.0, - 0.1882352941, - 0.047058823529411764, - 0.2117647059, - 0.0, - 0.2078431373, - 0.050980392156862744, - 0.2274509804, - 0.0, - 0.231372549, - 0.054901960784313725, - 0.2392156863, - 0.0, - 0.2470588235, - 0.05882352941176471, - 0.2509803922, - 0.0, - 0.2666666667, - 0.06274509803921569, - 0.2666666667, - 0.0, - 0.2823529412, - 0.06666666666666667, - 0.2705882353, - 0.0, - 0.3019607843, - 0.07058823529411765, - 0.2823529412, - 0.0, - 0.3176470588, - 0.07450980392156863, - 0.2901960784, - 0.0, - 0.337254902, - 0.0784313725490196, - 0.3019607843, - 0.0, - 0.3568627451, - 0.08235294117647059, - 0.3098039216, - 0.0, - 0.3725490196, - 0.08627450980392157, - 0.3137254902, - 0.0, - 0.3921568627, - 0.09019607843137255, - 0.3215686275, - 0.0, - 0.4078431373, - 0.09411764705882353, - 0.3254901961, - 0.0, - 0.4274509804, - 0.09803921568627451, - 0.3333333333, - 0.0, - 0.4431372549, - 0.10196078431372549, - 0.3294117647, - 0.0, - 0.462745098, - 0.10588235294117647, - 0.337254902, - 0.0, - 0.4784313725, - 0.10980392156862745, - 0.3411764706, - 0.0, - 0.4980392157, - 0.11372549019607843, - 0.3450980392, - 0.0, - 0.5176470588, - 0.11764705882352942, - 0.337254902, - 0.0, - 0.5333333333, - 0.12156862745098039, - 0.3411764706, - 0.0, - 0.5529411765, - 0.12549019607843137, - 0.3411764706, - 0.0, - 0.568627451, - 0.12941176470588237, - 0.3411764706, - 0.0, - 0.5882352941, - 0.13333333333333333, - 0.3333333333, - 0.0, - 0.6039215686, - 0.13725490196078433, - 0.3294117647, - 0.0, - 0.6235294118, - 0.1411764705882353, - 0.3294117647, - 0.0, - 0.6392156863, - 0.1450980392156863, - 0.3294117647, - 0.0, - 0.6588235294, - 0.14901960784313725, - 0.3254901961, - 0.0, - 0.6784313725, - 0.15294117647058825, - 0.3098039216, - 0.0, - 0.6941176471, - 0.1568627450980392, - 0.3058823529, - 0.0, - 0.7137254902, - 0.1607843137254902, - 0.3019607843, - 0.0, - 0.7294117647, - 0.16470588235294117, - 0.2980392157, - 0.0, - 0.7490196078, - 0.16862745098039217, - 0.2784313725, - 0.0, - 0.7647058824, - 0.17254901960784313, - 0.2745098039, - 0.0, - 0.7843137255, - 0.17647058823529413, - 0.2666666667, - 0.0, - 0.8, - 0.1803921568627451, - 0.2588235294, - 0.0, - 0.8196078431, - 0.1843137254901961, - 0.2352941176, - 0.0, - 0.8392156863, - 0.18823529411764706, - 0.2274509804, - 0.0, - 0.8549019608, - 0.19215686274509805, - 0.2156862745, - 0.0, - 0.8745098039, - 0.19607843137254902, - 0.2078431373, - 0.0, - 0.8901960784, - 0.2, - 0.1803921569, - 0.0, - 0.9098039216, - 0.20392156862745098, - 0.168627451, - 0.0, - 0.9254901961, - 0.20784313725490197, - 0.1568627451, - 0.0, - 0.9450980392, - 0.21176470588235294, - 0.1411764706, - 0.0, - 0.9607843137, - 0.21568627450980393, - 0.1294117647, - 0.0, - 0.9803921569, - 0.2196078431372549, - 0.0980392157, - 0.0, - 1.0, - 0.2235294117647059, - 0.0823529412, - 0.0, - 1.0, - 0.22745098039215686, - 0.062745098, - 0.0, - 1.0, - 0.23137254901960785, - 0.0470588235, - 0.0, - 1.0, - 0.23529411764705885, - 0.0156862745, - 0.0, - 1.0, - 0.23921568627450984, - 0.0, - 0.0, - 1.0, - 0.24313725490196078, - 0.0, - 0.0156862745, - 1.0, - 0.24705882352941178, - 0.0, - 0.031372549, - 1.0, - 0.25098039215686274, - 0.0, - 0.062745098, - 1.0, - 0.2549019607843137, - 0.0, - 0.0823529412, - 1.0, - 0.25882352941176473, - 0.0, - 0.0980392157, - 1.0, - 0.2627450980392157, - 0.0, - 0.1137254902, - 1.0, - 0.26666666666666666, - 0.0, - 0.1490196078, - 1.0, - 0.27058823529411763, - 0.0, - 0.1647058824, - 1.0, - 0.27450980392156865, - 0.0, - 0.1803921569, - 1.0, - 0.2784313725490196, - 0.0, - 0.2, - 1.0, - 0.2823529411764706, - 0.0, - 0.2156862745, - 1.0, - 0.28627450980392155, - 0.0, - 0.2470588235, - 1.0, - 0.2901960784313726, - 0.0, - 0.262745098, - 1.0, - 0.29411764705882354, - 0.0, - 0.2823529412, - 1.0, - 0.2980392156862745, - 0.0, - 0.2980392157, - 1.0, - 0.30196078431372547, - 0.0, - 0.3294117647, - 1.0, - 0.3058823529411765, - 0.0, - 0.3490196078, - 1.0, - 0.30980392156862746, - 0.0, - 0.3647058824, - 1.0, - 0.3137254901960784, - 0.0, - 0.3803921569, - 1.0, - 0.3176470588235294, - 0.0, - 0.4156862745, - 1.0, - 0.3215686274509804, - 0.0, - 0.431372549, - 1.0, - 0.3254901960784314, - 0.0, - 0.4470588235, - 1.0, - 0.32941176470588235, - 0.0, - 0.4666666667, - 1.0, - 0.3333333333333333, - 0.0, - 0.4980392157, - 1.0, - 0.33725490196078434, - 0.0, - 0.5137254902, - 1.0, - 0.3411764705882353, - 0.0, - 0.5294117647, - 1.0, - 0.34509803921568627, - 0.0, - 0.5490196078, - 1.0, - 0.34901960784313724, - 0.0, - 0.5647058824, - 1.0, - 0.35294117647058826, - 0.0, - 0.5960784314, - 1.0, - 0.3568627450980392, - 0.0, - 0.6156862745, - 1.0, - 0.3607843137254902, - 0.0, - 0.631372549, - 1.0, - 0.36470588235294116, - 0.0, - 0.6470588235, - 1.0, - 0.3686274509803922, - 0.0, - 0.6823529412, - 1.0, - 0.37254901960784315, - 0.0, - 0.6980392157, - 1.0, - 0.3764705882352941, - 0.0, - 0.7137254902, - 1.0, - 0.3803921568627451, - 0.0, - 0.7333333333, - 1.0, - 0.3843137254901961, - 0.0, - 0.7647058824, - 1.0, - 0.38823529411764707, - 0.0, - 0.7803921569, - 1.0, - 0.39215686274509803, - 0.0, - 0.7960784314, - 1.0, - 0.396078431372549, - 0.0, - 0.8156862745, - 1.0, - 0.4, - 0.0, - 0.8470588235, - 1.0, - 0.403921568627451, - 0.0, - 0.862745098, - 1.0, - 0.40784313725490196, - 0.0, - 0.8823529412, - 1.0, - 0.4117647058823529, - 0.0, - 0.8980392157, - 1.0, - 0.41568627450980394, - 0.0, - 0.9137254902, - 1.0, - 0.4196078431372549, - 0.0, - 0.9490196078, - 1.0, - 0.4235294117647059, - 0.0, - 0.9647058824, - 1.0, - 0.42745098039215684, - 0.0, - 0.9803921569, - 1.0, - 0.43137254901960786, - 0.0, - 1.0, - 1.0, - 0.43529411764705883, - 0.0, - 1.0, - 0.9647058824, - 0.4392156862745098, - 0.0, - 1.0, - 0.9490196078, - 0.44313725490196076, - 0.0, - 1.0, - 0.9333333333, - 0.4470588235294118, - 0.0, - 1.0, - 0.9137254902, - 0.45098039215686275, - 0.0, - 1.0, - 0.8823529412, - 0.4549019607843137, - 0.0, - 1.0, - 0.862745098, - 0.4588235294117647, - 0.0, - 1.0, - 0.8470588235, - 0.4627450980392157, - 0.0, - 1.0, - 0.831372549, - 0.4666666666666667, - 0.0, - 1.0, - 0.7960784314, - 0.4705882352941177, - 0.0, - 1.0, - 0.7803921569, - 0.4745098039215686, - 0.0, - 1.0, - 0.7647058824, - 0.4784313725490197, - 0.0, - 1.0, - 0.7490196078, - 0.48235294117647065, - 0.0, - 1.0, - 0.7333333333, - 0.48627450980392156, - 0.0, - 1.0, - 0.6980392157, - 0.49019607843137253, - 0.0, - 1.0, - 0.6823529412, - 0.49411764705882355, - 0.0, - 1.0, - 0.6666666667, - 0.4980392156862745, - 0.0, - 1.0, - 0.6470588235, - 0.5019607843137255, - 0.0, - 1.0, - 0.6156862745, - 0.5058823529411764, - 0.0, - 1.0, - 0.5960784314, - 0.5098039215686274, - 0.0, - 1.0, - 0.5803921569, - 0.5137254901960784, - 0.0, - 1.0, - 0.5647058824, - 0.5176470588235295, - 0.0, - 1.0, - 0.5294117647, - 0.5215686274509804, - 0.0, - 1.0, - 0.5137254902, - 0.5254901960784314, - 0.0, - 1.0, - 0.4980392157, - 0.5294117647058824, - 0.0, - 1.0, - 0.4823529412, - 0.5333333333333333, - 0.0, - 1.0, - 0.4470588235, - 0.5372549019607843, - 0.0, - 1.0, - 0.431372549, - 0.5411764705882353, - 0.0, - 1.0, - 0.4156862745, - 0.5450980392156862, - 0.0, - 1.0, - 0.4, - 0.5490196078431373, - 0.0, - 1.0, - 0.3803921569, - 0.5529411764705883, - 0.0, - 1.0, - 0.3490196078, - 0.5568627450980392, - 0.0, - 1.0, - 0.3294117647, - 0.5607843137254902, - 0.0, - 1.0, - 0.3137254902, - 0.5647058823529412, - 0.0, - 1.0, - 0.2980392157, - 0.5686274509803921, - 0.0, - 1.0, - 0.262745098, - 0.5725490196078431, - 0.0, - 1.0, - 0.2470588235, - 0.5764705882352941, - 0.0, - 1.0, - 0.231372549, - 0.5803921568627451, - 0.0, - 1.0, - 0.2156862745, - 0.5843137254901961, - 0.0, - 1.0, - 0.1803921569, - 0.5882352941176471, - 0.0, - 1.0, - 0.1647058824, - 0.592156862745098, - 0.0, - 1.0, - 0.1490196078, - 0.596078431372549, - 0.0, - 1.0, - 0.1333333333, - 0.6, - 0.0, - 1.0, - 0.0980392157, - 0.6039215686274509, - 0.0, - 1.0, - 0.0823529412, - 0.6078431372549019, - 0.0, - 1.0, - 0.062745098, - 0.611764705882353, - 0.0, - 1.0, - 0.0470588235, - 0.615686274509804, - 0.0, - 1.0, - 0.031372549, - 0.6196078431372549, - 0.0, - 1.0, - 0.0, - 0.6235294117647059, - 0.0156862745, - 1.0, - 0.0, - 0.6274509803921569, - 0.031372549, - 1.0, - 0.0, - 0.6313725490196078, - 0.0470588235, - 1.0, - 0.0, - 0.6352941176470588, - 0.0823529412, - 1.0, - 0.0, - 0.6392156862745098, - 0.0980392157, - 1.0, - 0.0, - 0.6431372549019608, - 0.1137254902, - 1.0, - 0.0, - 0.6470588235294118, - 0.1294117647, - 1.0, - 0.0, - 0.6509803921568628, - 0.1647058824, - 1.0, - 0.0, - 0.6549019607843137, - 0.1803921569, - 1.0, - 0.0, - 0.6588235294117647, - 0.2, - 1.0, - 0.0, - 0.6627450980392157, - 0.2156862745, - 1.0, - 0.0, - 0.6666666666666666, - 0.2470588235, - 1.0, - 0.0, - 0.6705882352941176, - 0.262745098, - 1.0, - 0.0, - 0.6745098039215687, - 0.2823529412, - 1.0, - 0.0, - 0.6784313725490196, - 0.2980392157, - 1.0, - 0.0, - 0.6823529411764706, - 0.3137254902, - 1.0, - 0.0, - 0.6862745098039216, - 0.3490196078, - 1.0, - 0.0, - 0.6901960784313725, - 0.3647058824, - 1.0, - 0.0, - 0.6941176470588235, - 0.3803921569, - 1.0, - 0.0, - 0.6980392156862745, - 0.3960784314, - 1.0, - 0.0, - 0.7019607843137254, - 0.431372549, - 1.0, - 0.0, - 0.7058823529411765, - 0.4470588235, - 1.0, - 0.0, - 0.7098039215686275, - 0.4666666667, - 1.0, - 0.0, - 0.7137254901960784, - 0.4823529412, - 1.0, - 0.0, - 0.7176470588235294, - 0.5137254902, - 1.0, - 0.0, - 0.7215686274509804, - 0.5294117647, - 1.0, - 0.0, - 0.7254901960784313, - 0.5490196078, - 1.0, - 0.0, - 0.7294117647058823, - 0.5647058824, - 1.0, - 0.0, - 0.7333333333333333, - 0.6, - 1.0, - 0.0, - 0.7372549019607844, - 0.6156862745, - 1.0, - 0.0, - 0.7411764705882353, - 0.631372549, - 1.0, - 0.0, - 0.7450980392156863, - 0.6470588235, - 1.0, - 0.0, - 0.7490196078431373, - 0.662745098, - 1.0, - 0.0, - 0.7529411764705882, - 0.6980392157, - 1.0, - 0.0, - 0.7568627450980392, - 0.7137254902, - 1.0, - 0.0, - 0.7607843137254902, - 0.7333333333, - 1.0, - 0.0, - 0.7647058823529411, - 0.7490196078, - 1.0, - 0.0, - 0.7686274509803922, - 0.7803921569, - 1.0, - 0.0, - 0.7725490196078432, - 0.7960784314, - 1.0, - 0.0, - 0.7764705882352941, - 0.8156862745, - 1.0, - 0.0, - 0.7803921568627451, - 0.831372549, - 1.0, - 0.0, - 0.7843137254901961, - 0.8666666667, - 1.0, - 0.0, - 0.788235294117647, - 0.8823529412, - 1.0, - 0.0, - 0.792156862745098, - 0.8980392157, - 1.0, - 0.0, - 0.796078431372549, - 0.9137254902, - 1.0, - 0.0, - 0.8, - 0.9490196078, - 1.0, - 0.0, - 0.803921568627451, - 0.9647058824, - 1.0, - 0.0, - 0.807843137254902, - 0.9803921569, - 1.0, - 0.0, - 0.8117647058823529, - 1.0, - 1.0, - 0.0, - 0.8156862745098039, - 1.0, - 0.9803921569, - 0.0, - 0.8196078431372549, - 1.0, - 0.9490196078, - 0.0, - 0.8235294117647058, - 1.0, - 0.9333333333, - 0.0, - 0.8274509803921568, - 1.0, - 0.9137254902, - 0.0, - 0.8313725490196079, - 1.0, - 0.8980392157, - 0.0, - 0.8352941176470589, - 1.0, - 0.8666666667, - 0.0, - 0.8392156862745098, - 1.0, - 0.8470588235, - 0.0, - 0.8431372549019608, - 1.0, - 0.831372549, - 0.0, - 0.8470588235294118, - 1.0, - 0.8156862745, - 0.0, - 0.8509803921568627, - 1.0, - 0.7803921569, - 0.0, - 0.8549019607843137, - 1.0, - 0.7647058824, - 0.0, - 0.8588235294117647, - 1.0, - 0.7490196078, - 0.0, - 0.8627450980392157, - 1.0, - 0.7333333333, - 0.0, - 0.8666666666666667, - 1.0, - 0.6980392157, - 0.0, - 0.8705882352941177, - 1.0, - 0.6823529412, - 0.0, - 0.8745098039215686, - 1.0, - 0.6666666667, - 0.0, - 0.8784313725490196, - 1.0, - 0.6470588235, - 0.0, - 0.8823529411764706, - 1.0, - 0.631372549, - 0.0, - 0.8862745098039215, - 1.0, - 0.6, - 0.0, - 0.8901960784313725, - 1.0, - 0.5803921569, - 0.0, - 0.8941176470588236, - 1.0, - 0.5647058824, - 0.0, - 0.8980392156862745, - 1.0, - 0.5490196078, - 0.0, - 0.9019607843137255, - 1.0, - 0.5137254902, - 0.0, - 0.9058823529411765, - 1.0, - 0.4980392157, - 0.0, - 0.9098039215686274, - 1.0, - 0.4823529412, - 0.0, - 0.9137254901960784, - 1.0, - 0.4666666667, - 0.0, - 0.9176470588235294, - 1.0, - 0.431372549, - 0.0, - 0.9215686274509803, - 1.0, - 0.4156862745, - 0.0, - 0.9254901960784314, - 1.0, - 0.4, - 0.0, - 0.9294117647058824, - 1.0, - 0.3803921569, - 0.0, - 0.9333333333333333, - 1.0, - 0.3490196078, - 0.0, - 0.9372549019607843, - 1.0, - 0.3333333333, - 0.0, - 0.9411764705882354, - 1.0, - 0.3137254902, - 0.0, - 0.9450980392156864, - 1.0, - 0.2980392157, - 0.0, - 0.9490196078431372, - 1.0, - 0.2823529412, - 0.0, - 0.9529411764705882, - 1.0, - 0.2470588235, - 0.0, - 0.9568627450980394, - 1.0, - 0.231372549, - 0.0, - 0.9607843137254903, - 1.0, - 0.2156862745, - 0.0, - 0.9647058823529413, - 1.0, - 0.2, - 0.0, - 0.9686274509803922, - 1.0, - 0.1647058824, - 0.0, - 0.9725490196078431, - 1.0, - 0.1490196078, - 0.0, - 0.9764705882352941, - 1.0, - 0.1333333333, - 0.0, - 0.9803921568627451, - 1.0, - 0.1137254902, - 0.0, - 0.984313725490196, - 1.0, - 0.0823529412, - 0.0, - 0.9882352941176471, - 1.0, - 0.0666666667, - 0.0, - 0.9921568627450981, - 1.0, - 0.0470588235, - 0.0, - 0.996078431372549, - 1.0, - 0.031372549, - 0.0, - 1.0, - 1.0, - 0.0, - 0.0, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0156862745, 0.0, 0.0117647059, 0.00784313725490196, + 0.0352941176, 0.0, 0.0274509804, 0.011764705882352941, 0.0509803922, 0.0, 0.0392156863, + 0.01568627450980392, 0.0705882353, 0.0, 0.0549019608, 0.0196078431372549, 0.0862745098, 0.0, + 0.0745098039, 0.023529411764705882, 0.1058823529, 0.0, 0.0901960784, 0.027450980392156862, + 0.1215686275, 0.0, 0.1098039216, 0.03137254901960784, 0.1411764706, 0.0, 0.1254901961, + 0.03529411764705882, 0.1568627451, 0.0, 0.1490196078, 0.0392156862745098, 0.1764705882, 0.0, + 0.168627451, 0.043137254901960784, 0.1960784314, 0.0, 0.1882352941, 0.047058823529411764, + 0.2117647059, 0.0, 0.2078431373, 0.050980392156862744, 0.2274509804, 0.0, 0.231372549, + 0.054901960784313725, 0.2392156863, 0.0, 0.2470588235, 0.05882352941176471, 0.2509803922, 0.0, + 0.2666666667, 0.06274509803921569, 0.2666666667, 0.0, 0.2823529412, 0.06666666666666667, + 0.2705882353, 0.0, 0.3019607843, 0.07058823529411765, 0.2823529412, 0.0, 0.3176470588, + 0.07450980392156863, 0.2901960784, 0.0, 0.337254902, 0.0784313725490196, 0.3019607843, 0.0, + 0.3568627451, 0.08235294117647059, 0.3098039216, 0.0, 0.3725490196, 0.08627450980392157, + 0.3137254902, 0.0, 0.3921568627, 0.09019607843137255, 0.3215686275, 0.0, 0.4078431373, + 0.09411764705882353, 0.3254901961, 0.0, 0.4274509804, 0.09803921568627451, 0.3333333333, 0.0, + 0.4431372549, 0.10196078431372549, 0.3294117647, 0.0, 0.462745098, 0.10588235294117647, + 0.337254902, 0.0, 0.4784313725, 0.10980392156862745, 0.3411764706, 0.0, 0.4980392157, + 0.11372549019607843, 0.3450980392, 0.0, 0.5176470588, 0.11764705882352942, 0.337254902, 0.0, + 0.5333333333, 0.12156862745098039, 0.3411764706, 0.0, 0.5529411765, 0.12549019607843137, + 0.3411764706, 0.0, 0.568627451, 0.12941176470588237, 0.3411764706, 0.0, 0.5882352941, + 0.13333333333333333, 0.3333333333, 0.0, 0.6039215686, 0.13725490196078433, 0.3294117647, 0.0, + 0.6235294118, 0.1411764705882353, 0.3294117647, 0.0, 0.6392156863, 0.1450980392156863, + 0.3294117647, 0.0, 0.6588235294, 0.14901960784313725, 0.3254901961, 0.0, 0.6784313725, + 0.15294117647058825, 0.3098039216, 0.0, 0.6941176471, 0.1568627450980392, 0.3058823529, 0.0, + 0.7137254902, 0.1607843137254902, 0.3019607843, 0.0, 0.7294117647, 0.16470588235294117, + 0.2980392157, 0.0, 0.7490196078, 0.16862745098039217, 0.2784313725, 0.0, 0.7647058824, + 0.17254901960784313, 0.2745098039, 0.0, 0.7843137255, 0.17647058823529413, 0.2666666667, 0.0, + 0.8, 0.1803921568627451, 0.2588235294, 0.0, 0.8196078431, 0.1843137254901961, 0.2352941176, + 0.0, 0.8392156863, 0.18823529411764706, 0.2274509804, 0.0, 0.8549019608, 0.19215686274509805, + 0.2156862745, 0.0, 0.8745098039, 0.19607843137254902, 0.2078431373, 0.0, 0.8901960784, 0.2, + 0.1803921569, 0.0, 0.9098039216, 0.20392156862745098, 0.168627451, 0.0, 0.9254901961, + 0.20784313725490197, 0.1568627451, 0.0, 0.9450980392, 0.21176470588235294, 0.1411764706, 0.0, + 0.9607843137, 0.21568627450980393, 0.1294117647, 0.0, 0.9803921569, 0.2196078431372549, + 0.0980392157, 0.0, 1.0, 0.2235294117647059, 0.0823529412, 0.0, 1.0, 0.22745098039215686, + 0.062745098, 0.0, 1.0, 0.23137254901960785, 0.0470588235, 0.0, 1.0, 0.23529411764705885, + 0.0156862745, 0.0, 1.0, 0.23921568627450984, 0.0, 0.0, 1.0, 0.24313725490196078, 0.0, + 0.0156862745, 1.0, 0.24705882352941178, 0.0, 0.031372549, 1.0, 0.25098039215686274, 0.0, + 0.062745098, 1.0, 0.2549019607843137, 0.0, 0.0823529412, 1.0, 0.25882352941176473, 0.0, + 0.0980392157, 1.0, 0.2627450980392157, 0.0, 0.1137254902, 1.0, 0.26666666666666666, 0.0, + 0.1490196078, 1.0, 0.27058823529411763, 0.0, 0.1647058824, 1.0, 0.27450980392156865, 0.0, + 0.1803921569, 1.0, 0.2784313725490196, 0.0, 0.2, 1.0, 0.2823529411764706, 0.0, 0.2156862745, + 1.0, 0.28627450980392155, 0.0, 0.2470588235, 1.0, 0.2901960784313726, 0.0, 0.262745098, 1.0, + 0.29411764705882354, 0.0, 0.2823529412, 1.0, 0.2980392156862745, 0.0, 0.2980392157, 1.0, + 0.30196078431372547, 0.0, 0.3294117647, 1.0, 0.3058823529411765, 0.0, 0.3490196078, 1.0, + 0.30980392156862746, 0.0, 0.3647058824, 1.0, 0.3137254901960784, 0.0, 0.3803921569, 1.0, + 0.3176470588235294, 0.0, 0.4156862745, 1.0, 0.3215686274509804, 0.0, 0.431372549, 1.0, + 0.3254901960784314, 0.0, 0.4470588235, 1.0, 0.32941176470588235, 0.0, 0.4666666667, 1.0, + 0.3333333333333333, 0.0, 0.4980392157, 1.0, 0.33725490196078434, 0.0, 0.5137254902, 1.0, + 0.3411764705882353, 0.0, 0.5294117647, 1.0, 0.34509803921568627, 0.0, 0.5490196078, 1.0, + 0.34901960784313724, 0.0, 0.5647058824, 1.0, 0.35294117647058826, 0.0, 0.5960784314, 1.0, + 0.3568627450980392, 0.0, 0.6156862745, 1.0, 0.3607843137254902, 0.0, 0.631372549, 1.0, + 0.36470588235294116, 0.0, 0.6470588235, 1.0, 0.3686274509803922, 0.0, 0.6823529412, 1.0, + 0.37254901960784315, 0.0, 0.6980392157, 1.0, 0.3764705882352941, 0.0, 0.7137254902, 1.0, + 0.3803921568627451, 0.0, 0.7333333333, 1.0, 0.3843137254901961, 0.0, 0.7647058824, 1.0, + 0.38823529411764707, 0.0, 0.7803921569, 1.0, 0.39215686274509803, 0.0, 0.7960784314, 1.0, + 0.396078431372549, 0.0, 0.8156862745, 1.0, 0.4, 0.0, 0.8470588235, 1.0, 0.403921568627451, + 0.0, 0.862745098, 1.0, 0.40784313725490196, 0.0, 0.8823529412, 1.0, 0.4117647058823529, 0.0, + 0.8980392157, 1.0, 0.41568627450980394, 0.0, 0.9137254902, 1.0, 0.4196078431372549, 0.0, + 0.9490196078, 1.0, 0.4235294117647059, 0.0, 0.9647058824, 1.0, 0.42745098039215684, 0.0, + 0.9803921569, 1.0, 0.43137254901960786, 0.0, 1.0, 1.0, 0.43529411764705883, 0.0, 1.0, + 0.9647058824, 0.4392156862745098, 0.0, 1.0, 0.9490196078, 0.44313725490196076, 0.0, 1.0, + 0.9333333333, 0.4470588235294118, 0.0, 1.0, 0.9137254902, 0.45098039215686275, 0.0, 1.0, + 0.8823529412, 0.4549019607843137, 0.0, 1.0, 0.862745098, 0.4588235294117647, 0.0, 1.0, + 0.8470588235, 0.4627450980392157, 0.0, 1.0, 0.831372549, 0.4666666666666667, 0.0, 1.0, + 0.7960784314, 0.4705882352941177, 0.0, 1.0, 0.7803921569, 0.4745098039215686, 0.0, 1.0, + 0.7647058824, 0.4784313725490197, 0.0, 1.0, 0.7490196078, 0.48235294117647065, 0.0, 1.0, + 0.7333333333, 0.48627450980392156, 0.0, 1.0, 0.6980392157, 0.49019607843137253, 0.0, 1.0, + 0.6823529412, 0.49411764705882355, 0.0, 1.0, 0.6666666667, 0.4980392156862745, 0.0, 1.0, + 0.6470588235, 0.5019607843137255, 0.0, 1.0, 0.6156862745, 0.5058823529411764, 0.0, 1.0, + 0.5960784314, 0.5098039215686274, 0.0, 1.0, 0.5803921569, 0.5137254901960784, 0.0, 1.0, + 0.5647058824, 0.5176470588235295, 0.0, 1.0, 0.5294117647, 0.5215686274509804, 0.0, 1.0, + 0.5137254902, 0.5254901960784314, 0.0, 1.0, 0.4980392157, 0.5294117647058824, 0.0, 1.0, + 0.4823529412, 0.5333333333333333, 0.0, 1.0, 0.4470588235, 0.5372549019607843, 0.0, 1.0, + 0.431372549, 0.5411764705882353, 0.0, 1.0, 0.4156862745, 0.5450980392156862, 0.0, 1.0, 0.4, + 0.5490196078431373, 0.0, 1.0, 0.3803921569, 0.5529411764705883, 0.0, 1.0, 0.3490196078, + 0.5568627450980392, 0.0, 1.0, 0.3294117647, 0.5607843137254902, 0.0, 1.0, 0.3137254902, + 0.5647058823529412, 0.0, 1.0, 0.2980392157, 0.5686274509803921, 0.0, 1.0, 0.262745098, + 0.5725490196078431, 0.0, 1.0, 0.2470588235, 0.5764705882352941, 0.0, 1.0, 0.231372549, + 0.5803921568627451, 0.0, 1.0, 0.2156862745, 0.5843137254901961, 0.0, 1.0, 0.1803921569, + 0.5882352941176471, 0.0, 1.0, 0.1647058824, 0.592156862745098, 0.0, 1.0, 0.1490196078, + 0.596078431372549, 0.0, 1.0, 0.1333333333, 0.6, 0.0, 1.0, 0.0980392157, 0.6039215686274509, + 0.0, 1.0, 0.0823529412, 0.6078431372549019, 0.0, 1.0, 0.062745098, 0.611764705882353, 0.0, + 1.0, 0.0470588235, 0.615686274509804, 0.0, 1.0, 0.031372549, 0.6196078431372549, 0.0, 1.0, + 0.0, 0.6235294117647059, 0.0156862745, 1.0, 0.0, 0.6274509803921569, 0.031372549, 1.0, 0.0, + 0.6313725490196078, 0.0470588235, 1.0, 0.0, 0.6352941176470588, 0.0823529412, 1.0, 0.0, + 0.6392156862745098, 0.0980392157, 1.0, 0.0, 0.6431372549019608, 0.1137254902, 1.0, 0.0, + 0.6470588235294118, 0.1294117647, 1.0, 0.0, 0.6509803921568628, 0.1647058824, 1.0, 0.0, + 0.6549019607843137, 0.1803921569, 1.0, 0.0, 0.6588235294117647, 0.2, 1.0, 0.0, + 0.6627450980392157, 0.2156862745, 1.0, 0.0, 0.6666666666666666, 0.2470588235, 1.0, 0.0, + 0.6705882352941176, 0.262745098, 1.0, 0.0, 0.6745098039215687, 0.2823529412, 1.0, 0.0, + 0.6784313725490196, 0.2980392157, 1.0, 0.0, 0.6823529411764706, 0.3137254902, 1.0, 0.0, + 0.6862745098039216, 0.3490196078, 1.0, 0.0, 0.6901960784313725, 0.3647058824, 1.0, 0.0, + 0.6941176470588235, 0.3803921569, 1.0, 0.0, 0.6980392156862745, 0.3960784314, 1.0, 0.0, + 0.7019607843137254, 0.431372549, 1.0, 0.0, 0.7058823529411765, 0.4470588235, 1.0, 0.0, + 0.7098039215686275, 0.4666666667, 1.0, 0.0, 0.7137254901960784, 0.4823529412, 1.0, 0.0, + 0.7176470588235294, 0.5137254902, 1.0, 0.0, 0.7215686274509804, 0.5294117647, 1.0, 0.0, + 0.7254901960784313, 0.5490196078, 1.0, 0.0, 0.7294117647058823, 0.5647058824, 1.0, 0.0, + 0.7333333333333333, 0.6, 1.0, 0.0, 0.7372549019607844, 0.6156862745, 1.0, 0.0, + 0.7411764705882353, 0.631372549, 1.0, 0.0, 0.7450980392156863, 0.6470588235, 1.0, 0.0, + 0.7490196078431373, 0.662745098, 1.0, 0.0, 0.7529411764705882, 0.6980392157, 1.0, 0.0, + 0.7568627450980392, 0.7137254902, 1.0, 0.0, 0.7607843137254902, 0.7333333333, 1.0, 0.0, + 0.7647058823529411, 0.7490196078, 1.0, 0.0, 0.7686274509803922, 0.7803921569, 1.0, 0.0, + 0.7725490196078432, 0.7960784314, 1.0, 0.0, 0.7764705882352941, 0.8156862745, 1.0, 0.0, + 0.7803921568627451, 0.831372549, 1.0, 0.0, 0.7843137254901961, 0.8666666667, 1.0, 0.0, + 0.788235294117647, 0.8823529412, 1.0, 0.0, 0.792156862745098, 0.8980392157, 1.0, 0.0, + 0.796078431372549, 0.9137254902, 1.0, 0.0, 0.8, 0.9490196078, 1.0, 0.0, 0.803921568627451, + 0.9647058824, 1.0, 0.0, 0.807843137254902, 0.9803921569, 1.0, 0.0, 0.8117647058823529, 1.0, + 1.0, 0.0, 0.8156862745098039, 1.0, 0.9803921569, 0.0, 0.8196078431372549, 1.0, 0.9490196078, + 0.0, 0.8235294117647058, 1.0, 0.9333333333, 0.0, 0.8274509803921568, 1.0, 0.9137254902, 0.0, + 0.8313725490196079, 1.0, 0.8980392157, 0.0, 0.8352941176470589, 1.0, 0.8666666667, 0.0, + 0.8392156862745098, 1.0, 0.8470588235, 0.0, 0.8431372549019608, 1.0, 0.831372549, 0.0, + 0.8470588235294118, 1.0, 0.8156862745, 0.0, 0.8509803921568627, 1.0, 0.7803921569, 0.0, + 0.8549019607843137, 1.0, 0.7647058824, 0.0, 0.8588235294117647, 1.0, 0.7490196078, 0.0, + 0.8627450980392157, 1.0, 0.7333333333, 0.0, 0.8666666666666667, 1.0, 0.6980392157, 0.0, + 0.8705882352941177, 1.0, 0.6823529412, 0.0, 0.8745098039215686, 1.0, 0.6666666667, 0.0, + 0.8784313725490196, 1.0, 0.6470588235, 0.0, 0.8823529411764706, 1.0, 0.631372549, 0.0, + 0.8862745098039215, 1.0, 0.6, 0.0, 0.8901960784313725, 1.0, 0.5803921569, 0.0, + 0.8941176470588236, 1.0, 0.5647058824, 0.0, 0.8980392156862745, 1.0, 0.5490196078, 0.0, + 0.9019607843137255, 1.0, 0.5137254902, 0.0, 0.9058823529411765, 1.0, 0.4980392157, 0.0, + 0.9098039215686274, 1.0, 0.4823529412, 0.0, 0.9137254901960784, 1.0, 0.4666666667, 0.0, + 0.9176470588235294, 1.0, 0.431372549, 0.0, 0.9215686274509803, 1.0, 0.4156862745, 0.0, + 0.9254901960784314, 1.0, 0.4, 0.0, 0.9294117647058824, 1.0, 0.3803921569, 0.0, + 0.9333333333333333, 1.0, 0.3490196078, 0.0, 0.9372549019607843, 1.0, 0.3333333333, 0.0, + 0.9411764705882354, 1.0, 0.3137254902, 0.0, 0.9450980392156864, 1.0, 0.2980392157, 0.0, + 0.9490196078431372, 1.0, 0.2823529412, 0.0, 0.9529411764705882, 1.0, 0.2470588235, 0.0, + 0.9568627450980394, 1.0, 0.231372549, 0.0, 0.9607843137254903, 1.0, 0.2156862745, 0.0, + 0.9647058823529413, 1.0, 0.2, 0.0, 0.9686274509803922, 1.0, 0.1647058824, 0.0, + 0.9725490196078431, 1.0, 0.1490196078, 0.0, 0.9764705882352941, 1.0, 0.1333333333, 0.0, + 0.9803921568627451, 1.0, 0.1137254902, 0.0, 0.984313725490196, 1.0, 0.0823529412, 0.0, + 0.9882352941176471, 1.0, 0.0666666667, 0.0, 0.9921568627450981, 1.0, 0.0470588235, 0.0, + 0.996078431372549, 1.0, 0.031372549, 0.0, 1.0, 1.0, 0.0, 0.0, ], }, { ColorSpace: 'RGB', Name: 'suv', RGBPoints: [ - 0.0, - 1.0, - 1.0, - 1.0, - 0.00392156862745098, - 1.0, - 1.0, - 1.0, - 0.00784313725490196, - 1.0, - 1.0, - 1.0, - 0.011764705882352941, - 1.0, - 1.0, - 1.0, - 0.01568627450980392, - 1.0, - 1.0, - 1.0, - 0.0196078431372549, - 1.0, - 1.0, - 1.0, - 0.023529411764705882, - 1.0, - 1.0, - 1.0, - 0.027450980392156862, - 1.0, - 1.0, - 1.0, - 0.03137254901960784, - 1.0, - 1.0, - 1.0, - 0.03529411764705882, - 1.0, - 1.0, - 1.0, - 0.0392156862745098, - 1.0, - 1.0, - 1.0, - 0.043137254901960784, - 1.0, - 1.0, - 1.0, - 0.047058823529411764, - 1.0, - 1.0, - 1.0, - 0.050980392156862744, - 1.0, - 1.0, - 1.0, - 0.054901960784313725, - 1.0, - 1.0, - 1.0, - 0.05882352941176471, - 1.0, - 1.0, - 1.0, - 0.06274509803921569, - 1.0, - 1.0, - 1.0, - 0.06666666666666667, - 1.0, - 1.0, - 1.0, - 0.07058823529411765, - 1.0, - 1.0, - 1.0, - 0.07450980392156863, - 1.0, - 1.0, - 1.0, - 0.0784313725490196, - 1.0, - 1.0, - 1.0, - 0.08235294117647059, - 1.0, - 1.0, - 1.0, - 0.08627450980392157, - 1.0, - 1.0, - 1.0, - 0.09019607843137255, - 1.0, - 1.0, - 1.0, - 0.09411764705882353, - 1.0, - 1.0, - 1.0, - 0.09803921568627451, - 1.0, - 1.0, - 1.0, - 0.10196078431372549, - 0.737254902, - 0.737254902, - 0.737254902, - 0.10588235294117647, - 0.737254902, - 0.737254902, - 0.737254902, - 0.10980392156862745, - 0.737254902, - 0.737254902, - 0.737254902, - 0.11372549019607843, - 0.737254902, - 0.737254902, - 0.737254902, - 0.11764705882352942, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12156862745098039, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12549019607843137, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12941176470588237, - 0.737254902, - 0.737254902, - 0.737254902, - 0.13333333333333333, - 0.737254902, - 0.737254902, - 0.737254902, - 0.13725490196078433, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1411764705882353, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1450980392156863, - 0.737254902, - 0.737254902, - 0.737254902, - 0.14901960784313725, - 0.737254902, - 0.737254902, - 0.737254902, - 0.15294117647058825, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1568627450980392, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1607843137254902, - 0.737254902, - 0.737254902, - 0.737254902, - 0.16470588235294117, - 0.737254902, - 0.737254902, - 0.737254902, - 0.16862745098039217, - 0.737254902, - 0.737254902, - 0.737254902, - 0.17254901960784313, - 0.737254902, - 0.737254902, - 0.737254902, - 0.17647058823529413, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1803921568627451, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1843137254901961, - 0.737254902, - 0.737254902, - 0.737254902, - 0.18823529411764706, - 0.737254902, - 0.737254902, - 0.737254902, - 0.19215686274509805, - 0.737254902, - 0.737254902, - 0.737254902, - 0.19607843137254902, - 0.737254902, - 0.737254902, - 0.737254902, - 0.2, - 0.737254902, - 0.737254902, - 0.737254902, - 0.20392156862745098, - 0.431372549, - 0.0, - 0.568627451, - 0.20784313725490197, - 0.431372549, - 0.0, - 0.568627451, - 0.21176470588235294, - 0.431372549, - 0.0, - 0.568627451, - 0.21568627450980393, - 0.431372549, - 0.0, - 0.568627451, - 0.2196078431372549, - 0.431372549, - 0.0, - 0.568627451, - 0.2235294117647059, - 0.431372549, - 0.0, - 0.568627451, - 0.22745098039215686, - 0.431372549, - 0.0, - 0.568627451, - 0.23137254901960785, - 0.431372549, - 0.0, - 0.568627451, - 0.23529411764705885, - 0.431372549, - 0.0, - 0.568627451, - 0.23921568627450984, - 0.431372549, - 0.0, - 0.568627451, - 0.24313725490196078, - 0.431372549, - 0.0, - 0.568627451, - 0.24705882352941178, - 0.431372549, - 0.0, - 0.568627451, - 0.25098039215686274, - 0.431372549, - 0.0, - 0.568627451, - 0.2549019607843137, - 0.431372549, - 0.0, - 0.568627451, - 0.25882352941176473, - 0.431372549, - 0.0, - 0.568627451, - 0.2627450980392157, - 0.431372549, - 0.0, - 0.568627451, - 0.26666666666666666, - 0.431372549, - 0.0, - 0.568627451, - 0.27058823529411763, - 0.431372549, - 0.0, - 0.568627451, - 0.27450980392156865, - 0.431372549, - 0.0, - 0.568627451, - 0.2784313725490196, - 0.431372549, - 0.0, - 0.568627451, - 0.2823529411764706, - 0.431372549, - 0.0, - 0.568627451, - 0.28627450980392155, - 0.431372549, - 0.0, - 0.568627451, - 0.2901960784313726, - 0.431372549, - 0.0, - 0.568627451, - 0.29411764705882354, - 0.431372549, - 0.0, - 0.568627451, - 0.2980392156862745, - 0.431372549, - 0.0, - 0.568627451, - 0.30196078431372547, - 0.431372549, - 0.0, - 0.568627451, - 0.3058823529411765, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.30980392156862746, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3137254901960784, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3176470588235294, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3215686274509804, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3254901960784314, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.32941176470588235, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3333333333333333, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.33725490196078434, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3411764705882353, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.34509803921568627, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.34901960784313724, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.35294117647058826, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3568627450980392, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3607843137254902, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.36470588235294116, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3686274509803922, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.37254901960784315, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3764705882352941, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3803921568627451, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3843137254901961, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.38823529411764707, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.39215686274509803, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.396078431372549, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.4, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.403921568627451, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.40784313725490196, - 0.0, - 0.8, - 1.0, - 0.4117647058823529, - 0.0, - 0.8, - 1.0, - 0.41568627450980394, - 0.0, - 0.8, - 1.0, - 0.4196078431372549, - 0.0, - 0.8, - 1.0, - 0.4235294117647059, - 0.0, - 0.8, - 1.0, - 0.42745098039215684, - 0.0, - 0.8, - 1.0, - 0.43137254901960786, - 0.0, - 0.8, - 1.0, - 0.43529411764705883, - 0.0, - 0.8, - 1.0, - 0.4392156862745098, - 0.0, - 0.8, - 1.0, - 0.44313725490196076, - 0.0, - 0.8, - 1.0, - 0.4470588235294118, - 0.0, - 0.8, - 1.0, - 0.45098039215686275, - 0.0, - 0.8, - 1.0, - 0.4549019607843137, - 0.0, - 0.8, - 1.0, - 0.4588235294117647, - 0.0, - 0.8, - 1.0, - 0.4627450980392157, - 0.0, - 0.8, - 1.0, - 0.4666666666666667, - 0.0, - 0.8, - 1.0, - 0.4705882352941177, - 0.0, - 0.8, - 1.0, - 0.4745098039215686, - 0.0, - 0.8, - 1.0, - 0.4784313725490197, - 0.0, - 0.8, - 1.0, - 0.48235294117647065, - 0.0, - 0.8, - 1.0, - 0.48627450980392156, - 0.0, - 0.8, - 1.0, - 0.49019607843137253, - 0.0, - 0.8, - 1.0, - 0.49411764705882355, - 0.0, - 0.8, - 1.0, - 0.4980392156862745, - 0.0, - 0.8, - 1.0, - 0.5019607843137255, - 0.0, - 0.8, - 1.0, - 0.5058823529411764, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5098039215686274, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5137254901960784, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5176470588235295, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5215686274509804, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5254901960784314, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5294117647058824, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5333333333333333, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5372549019607843, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5411764705882353, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5450980392156862, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5490196078431373, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5529411764705883, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5568627450980392, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5607843137254902, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5647058823529412, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5686274509803921, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5725490196078431, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5764705882352941, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5803921568627451, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5843137254901961, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5882352941176471, - 0.0, - 0.6666666667, - 0.5333333333, - 0.592156862745098, - 0.0, - 0.6666666667, - 0.5333333333, - 0.596078431372549, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6039215686274509, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6078431372549019, - 0.4, - 1.0, - 0.4, - 0.611764705882353, - 0.4, - 1.0, - 0.4, - 0.615686274509804, - 0.4, - 1.0, - 0.4, - 0.6196078431372549, - 0.4, - 1.0, - 0.4, - 0.6235294117647059, - 0.4, - 1.0, - 0.4, - 0.6274509803921569, - 0.4, - 1.0, - 0.4, - 0.6313725490196078, - 0.4, - 1.0, - 0.4, - 0.6352941176470588, - 0.4, - 1.0, - 0.4, - 0.6392156862745098, - 0.4, - 1.0, - 0.4, - 0.6431372549019608, - 0.4, - 1.0, - 0.4, - 0.6470588235294118, - 0.4, - 1.0, - 0.4, - 0.6509803921568628, - 0.4, - 1.0, - 0.4, - 0.6549019607843137, - 0.4, - 1.0, - 0.4, - 0.6588235294117647, - 0.4, - 1.0, - 0.4, - 0.6627450980392157, - 0.4, - 1.0, - 0.4, - 0.6666666666666666, - 0.4, - 1.0, - 0.4, - 0.6705882352941176, - 0.4, - 1.0, - 0.4, - 0.6745098039215687, - 0.4, - 1.0, - 0.4, - 0.6784313725490196, - 0.4, - 1.0, - 0.4, - 0.6823529411764706, - 0.4, - 1.0, - 0.4, - 0.6862745098039216, - 0.4, - 1.0, - 0.4, - 0.6901960784313725, - 0.4, - 1.0, - 0.4, - 0.6941176470588235, - 0.4, - 1.0, - 0.4, - 0.6980392156862745, - 0.4, - 1.0, - 0.4, - 0.7019607843137254, - 0.4, - 1.0, - 0.4, - 0.7058823529411765, - 1.0, - 0.9490196078, - 0.0, - 0.7098039215686275, - 1.0, - 0.9490196078, - 0.0, - 0.7137254901960784, - 1.0, - 0.9490196078, - 0.0, - 0.7176470588235294, - 1.0, - 0.9490196078, - 0.0, - 0.7215686274509804, - 1.0, - 0.9490196078, - 0.0, - 0.7254901960784313, - 1.0, - 0.9490196078, - 0.0, - 0.7294117647058823, - 1.0, - 0.9490196078, - 0.0, - 0.7333333333333333, - 1.0, - 0.9490196078, - 0.0, - 0.7372549019607844, - 1.0, - 0.9490196078, - 0.0, - 0.7411764705882353, - 1.0, - 0.9490196078, - 0.0, - 0.7450980392156863, - 1.0, - 0.9490196078, - 0.0, - 0.7490196078431373, - 1.0, - 0.9490196078, - 0.0, - 0.7529411764705882, - 1.0, - 0.9490196078, - 0.0, - 0.7568627450980392, - 1.0, - 0.9490196078, - 0.0, - 0.7607843137254902, - 1.0, - 0.9490196078, - 0.0, - 0.7647058823529411, - 1.0, - 0.9490196078, - 0.0, - 0.7686274509803922, - 1.0, - 0.9490196078, - 0.0, - 0.7725490196078432, - 1.0, - 0.9490196078, - 0.0, - 0.7764705882352941, - 1.0, - 0.9490196078, - 0.0, - 0.7803921568627451, - 1.0, - 0.9490196078, - 0.0, - 0.7843137254901961, - 1.0, - 0.9490196078, - 0.0, - 0.788235294117647, - 1.0, - 0.9490196078, - 0.0, - 0.792156862745098, - 1.0, - 0.9490196078, - 0.0, - 0.796078431372549, - 1.0, - 0.9490196078, - 0.0, - 0.8, - 1.0, - 0.9490196078, - 0.0, - 0.803921568627451, - 1.0, - 0.9490196078, - 0.0, - 0.807843137254902, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8117647058823529, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8156862745098039, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8196078431372549, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8235294117647058, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8274509803921568, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8313725490196079, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8352941176470589, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8392156862745098, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8431372549019608, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8470588235294118, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8509803921568627, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8549019607843137, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8588235294117647, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8627450980392157, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8666666666666667, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8705882352941177, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8745098039215686, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8784313725490196, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8823529411764706, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8862745098039215, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8901960784313725, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8941176470588236, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8980392156862745, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9019607843137255, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9058823529411765, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9098039215686274, - 1.0, - 0.0, - 0.0, - 0.9137254901960784, - 1.0, - 0.0, - 0.0, - 0.9176470588235294, - 1.0, - 0.0, - 0.0, - 0.9215686274509803, - 1.0, - 0.0, - 0.0, - 0.9254901960784314, - 1.0, - 0.0, - 0.0, - 0.9294117647058824, - 1.0, - 0.0, - 0.0, - 0.9333333333333333, - 1.0, - 0.0, - 0.0, - 0.9372549019607843, - 1.0, - 0.0, - 0.0, - 0.9411764705882354, - 1.0, - 0.0, - 0.0, - 0.9450980392156864, - 1.0, - 0.0, - 0.0, - 0.9490196078431372, - 1.0, - 0.0, - 0.0, - 0.9529411764705882, - 1.0, - 0.0, - 0.0, - 0.9568627450980394, - 1.0, - 0.0, - 0.0, - 0.9607843137254903, - 1.0, - 0.0, - 0.0, - 0.9647058823529413, - 1.0, - 0.0, - 0.0, - 0.9686274509803922, - 1.0, - 0.0, - 0.0, - 0.9725490196078431, - 1.0, - 0.0, - 0.0, - 0.9764705882352941, - 1.0, - 0.0, - 0.0, - 0.9803921568627451, - 1.0, - 0.0, - 0.0, - 0.984313725490196, - 1.0, - 0.0, - 0.0, - 0.9882352941176471, - 1.0, - 0.0, - 0.0, - 0.9921568627450981, - 1.0, - 0.0, - 0.0, - 0.996078431372549, - 1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 0.0, - 0.0, + 0.0, 1.0, 1.0, 1.0, 0.00392156862745098, 1.0, 1.0, 1.0, 0.00784313725490196, 1.0, 1.0, 1.0, + 0.011764705882352941, 1.0, 1.0, 1.0, 0.01568627450980392, 1.0, 1.0, 1.0, 0.0196078431372549, + 1.0, 1.0, 1.0, 0.023529411764705882, 1.0, 1.0, 1.0, 0.027450980392156862, 1.0, 1.0, 1.0, + 0.03137254901960784, 1.0, 1.0, 1.0, 0.03529411764705882, 1.0, 1.0, 1.0, 0.0392156862745098, + 1.0, 1.0, 1.0, 0.043137254901960784, 1.0, 1.0, 1.0, 0.047058823529411764, 1.0, 1.0, 1.0, + 0.050980392156862744, 1.0, 1.0, 1.0, 0.054901960784313725, 1.0, 1.0, 1.0, 0.05882352941176471, + 1.0, 1.0, 1.0, 0.06274509803921569, 1.0, 1.0, 1.0, 0.06666666666666667, 1.0, 1.0, 1.0, + 0.07058823529411765, 1.0, 1.0, 1.0, 0.07450980392156863, 1.0, 1.0, 1.0, 0.0784313725490196, + 1.0, 1.0, 1.0, 0.08235294117647059, 1.0, 1.0, 1.0, 0.08627450980392157, 1.0, 1.0, 1.0, + 0.09019607843137255, 1.0, 1.0, 1.0, 0.09411764705882353, 1.0, 1.0, 1.0, 0.09803921568627451, + 1.0, 1.0, 1.0, 0.10196078431372549, 0.737254902, 0.737254902, 0.737254902, + 0.10588235294117647, 0.737254902, 0.737254902, 0.737254902, 0.10980392156862745, 0.737254902, + 0.737254902, 0.737254902, 0.11372549019607843, 0.737254902, 0.737254902, 0.737254902, + 0.11764705882352942, 0.737254902, 0.737254902, 0.737254902, 0.12156862745098039, 0.737254902, + 0.737254902, 0.737254902, 0.12549019607843137, 0.737254902, 0.737254902, 0.737254902, + 0.12941176470588237, 0.737254902, 0.737254902, 0.737254902, 0.13333333333333333, 0.737254902, + 0.737254902, 0.737254902, 0.13725490196078433, 0.737254902, 0.737254902, 0.737254902, + 0.1411764705882353, 0.737254902, 0.737254902, 0.737254902, 0.1450980392156863, 0.737254902, + 0.737254902, 0.737254902, 0.14901960784313725, 0.737254902, 0.737254902, 0.737254902, + 0.15294117647058825, 0.737254902, 0.737254902, 0.737254902, 0.1568627450980392, 0.737254902, + 0.737254902, 0.737254902, 0.1607843137254902, 0.737254902, 0.737254902, 0.737254902, + 0.16470588235294117, 0.737254902, 0.737254902, 0.737254902, 0.16862745098039217, 0.737254902, + 0.737254902, 0.737254902, 0.17254901960784313, 0.737254902, 0.737254902, 0.737254902, + 0.17647058823529413, 0.737254902, 0.737254902, 0.737254902, 0.1803921568627451, 0.737254902, + 0.737254902, 0.737254902, 0.1843137254901961, 0.737254902, 0.737254902, 0.737254902, + 0.18823529411764706, 0.737254902, 0.737254902, 0.737254902, 0.19215686274509805, 0.737254902, + 0.737254902, 0.737254902, 0.19607843137254902, 0.737254902, 0.737254902, 0.737254902, 0.2, + 0.737254902, 0.737254902, 0.737254902, 0.20392156862745098, 0.431372549, 0.0, 0.568627451, + 0.20784313725490197, 0.431372549, 0.0, 0.568627451, 0.21176470588235294, 0.431372549, 0.0, + 0.568627451, 0.21568627450980393, 0.431372549, 0.0, 0.568627451, 0.2196078431372549, + 0.431372549, 0.0, 0.568627451, 0.2235294117647059, 0.431372549, 0.0, 0.568627451, + 0.22745098039215686, 0.431372549, 0.0, 0.568627451, 0.23137254901960785, 0.431372549, 0.0, + 0.568627451, 0.23529411764705885, 0.431372549, 0.0, 0.568627451, 0.23921568627450984, + 0.431372549, 0.0, 0.568627451, 0.24313725490196078, 0.431372549, 0.0, 0.568627451, + 0.24705882352941178, 0.431372549, 0.0, 0.568627451, 0.25098039215686274, 0.431372549, 0.0, + 0.568627451, 0.2549019607843137, 0.431372549, 0.0, 0.568627451, 0.25882352941176473, + 0.431372549, 0.0, 0.568627451, 0.2627450980392157, 0.431372549, 0.0, 0.568627451, + 0.26666666666666666, 0.431372549, 0.0, 0.568627451, 0.27058823529411763, 0.431372549, 0.0, + 0.568627451, 0.27450980392156865, 0.431372549, 0.0, 0.568627451, 0.2784313725490196, + 0.431372549, 0.0, 0.568627451, 0.2823529411764706, 0.431372549, 0.0, 0.568627451, + 0.28627450980392155, 0.431372549, 0.0, 0.568627451, 0.2901960784313726, 0.431372549, 0.0, + 0.568627451, 0.29411764705882354, 0.431372549, 0.0, 0.568627451, 0.2980392156862745, + 0.431372549, 0.0, 0.568627451, 0.30196078431372547, 0.431372549, 0.0, 0.568627451, + 0.3058823529411765, 0.2509803922, 0.3333333333, 0.6509803922, 0.30980392156862746, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3137254901960784, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3176470588235294, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3215686274509804, 0.2509803922, 0.3333333333, 0.6509803922, 0.3254901960784314, + 0.2509803922, 0.3333333333, 0.6509803922, 0.32941176470588235, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3333333333333333, 0.2509803922, 0.3333333333, 0.6509803922, + 0.33725490196078434, 0.2509803922, 0.3333333333, 0.6509803922, 0.3411764705882353, + 0.2509803922, 0.3333333333, 0.6509803922, 0.34509803921568627, 0.2509803922, 0.3333333333, + 0.6509803922, 0.34901960784313724, 0.2509803922, 0.3333333333, 0.6509803922, + 0.35294117647058826, 0.2509803922, 0.3333333333, 0.6509803922, 0.3568627450980392, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3607843137254902, 0.2509803922, 0.3333333333, + 0.6509803922, 0.36470588235294116, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3686274509803922, 0.2509803922, 0.3333333333, 0.6509803922, 0.37254901960784315, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3764705882352941, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3803921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3843137254901961, 0.2509803922, 0.3333333333, 0.6509803922, 0.38823529411764707, + 0.2509803922, 0.3333333333, 0.6509803922, 0.39215686274509803, 0.2509803922, 0.3333333333, + 0.6509803922, 0.396078431372549, 0.2509803922, 0.3333333333, 0.6509803922, 0.4, 0.2509803922, + 0.3333333333, 0.6509803922, 0.403921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.40784313725490196, 0.0, 0.8, 1.0, 0.4117647058823529, 0.0, 0.8, 1.0, 0.41568627450980394, + 0.0, 0.8, 1.0, 0.4196078431372549, 0.0, 0.8, 1.0, 0.4235294117647059, 0.0, 0.8, 1.0, + 0.42745098039215684, 0.0, 0.8, 1.0, 0.43137254901960786, 0.0, 0.8, 1.0, 0.43529411764705883, + 0.0, 0.8, 1.0, 0.4392156862745098, 0.0, 0.8, 1.0, 0.44313725490196076, 0.0, 0.8, 1.0, + 0.4470588235294118, 0.0, 0.8, 1.0, 0.45098039215686275, 0.0, 0.8, 1.0, 0.4549019607843137, + 0.0, 0.8, 1.0, 0.4588235294117647, 0.0, 0.8, 1.0, 0.4627450980392157, 0.0, 0.8, 1.0, + 0.4666666666666667, 0.0, 0.8, 1.0, 0.4705882352941177, 0.0, 0.8, 1.0, 0.4745098039215686, 0.0, + 0.8, 1.0, 0.4784313725490197, 0.0, 0.8, 1.0, 0.48235294117647065, 0.0, 0.8, 1.0, + 0.48627450980392156, 0.0, 0.8, 1.0, 0.49019607843137253, 0.0, 0.8, 1.0, 0.49411764705882355, + 0.0, 0.8, 1.0, 0.4980392156862745, 0.0, 0.8, 1.0, 0.5019607843137255, 0.0, 0.8, 1.0, + 0.5058823529411764, 0.0, 0.6666666667, 0.5333333333, 0.5098039215686274, 0.0, 0.6666666667, + 0.5333333333, 0.5137254901960784, 0.0, 0.6666666667, 0.5333333333, 0.5176470588235295, 0.0, + 0.6666666667, 0.5333333333, 0.5215686274509804, 0.0, 0.6666666667, 0.5333333333, + 0.5254901960784314, 0.0, 0.6666666667, 0.5333333333, 0.5294117647058824, 0.0, 0.6666666667, + 0.5333333333, 0.5333333333333333, 0.0, 0.6666666667, 0.5333333333, 0.5372549019607843, 0.0, + 0.6666666667, 0.5333333333, 0.5411764705882353, 0.0, 0.6666666667, 0.5333333333, + 0.5450980392156862, 0.0, 0.6666666667, 0.5333333333, 0.5490196078431373, 0.0, 0.6666666667, + 0.5333333333, 0.5529411764705883, 0.0, 0.6666666667, 0.5333333333, 0.5568627450980392, 0.0, + 0.6666666667, 0.5333333333, 0.5607843137254902, 0.0, 0.6666666667, 0.5333333333, + 0.5647058823529412, 0.0, 0.6666666667, 0.5333333333, 0.5686274509803921, 0.0, 0.6666666667, + 0.5333333333, 0.5725490196078431, 0.0, 0.6666666667, 0.5333333333, 0.5764705882352941, 0.0, + 0.6666666667, 0.5333333333, 0.5803921568627451, 0.0, 0.6666666667, 0.5333333333, + 0.5843137254901961, 0.0, 0.6666666667, 0.5333333333, 0.5882352941176471, 0.0, 0.6666666667, + 0.5333333333, 0.592156862745098, 0.0, 0.6666666667, 0.5333333333, 0.596078431372549, 0.0, + 0.6666666667, 0.5333333333, 0.6, 0.0, 0.6666666667, 0.5333333333, 0.6039215686274509, 0.0, + 0.6666666667, 0.5333333333, 0.6078431372549019, 0.4, 1.0, 0.4, 0.611764705882353, 0.4, 1.0, + 0.4, 0.615686274509804, 0.4, 1.0, 0.4, 0.6196078431372549, 0.4, 1.0, 0.4, 0.6235294117647059, + 0.4, 1.0, 0.4, 0.6274509803921569, 0.4, 1.0, 0.4, 0.6313725490196078, 0.4, 1.0, 0.4, + 0.6352941176470588, 0.4, 1.0, 0.4, 0.6392156862745098, 0.4, 1.0, 0.4, 0.6431372549019608, 0.4, + 1.0, 0.4, 0.6470588235294118, 0.4, 1.0, 0.4, 0.6509803921568628, 0.4, 1.0, 0.4, + 0.6549019607843137, 0.4, 1.0, 0.4, 0.6588235294117647, 0.4, 1.0, 0.4, 0.6627450980392157, 0.4, + 1.0, 0.4, 0.6666666666666666, 0.4, 1.0, 0.4, 0.6705882352941176, 0.4, 1.0, 0.4, + 0.6745098039215687, 0.4, 1.0, 0.4, 0.6784313725490196, 0.4, 1.0, 0.4, 0.6823529411764706, 0.4, + 1.0, 0.4, 0.6862745098039216, 0.4, 1.0, 0.4, 0.6901960784313725, 0.4, 1.0, 0.4, + 0.6941176470588235, 0.4, 1.0, 0.4, 0.6980392156862745, 0.4, 1.0, 0.4, 0.7019607843137254, 0.4, + 1.0, 0.4, 0.7058823529411765, 1.0, 0.9490196078, 0.0, 0.7098039215686275, 1.0, 0.9490196078, + 0.0, 0.7137254901960784, 1.0, 0.9490196078, 0.0, 0.7176470588235294, 1.0, 0.9490196078, 0.0, + 0.7215686274509804, 1.0, 0.9490196078, 0.0, 0.7254901960784313, 1.0, 0.9490196078, 0.0, + 0.7294117647058823, 1.0, 0.9490196078, 0.0, 0.7333333333333333, 1.0, 0.9490196078, 0.0, + 0.7372549019607844, 1.0, 0.9490196078, 0.0, 0.7411764705882353, 1.0, 0.9490196078, 0.0, + 0.7450980392156863, 1.0, 0.9490196078, 0.0, 0.7490196078431373, 1.0, 0.9490196078, 0.0, + 0.7529411764705882, 1.0, 0.9490196078, 0.0, 0.7568627450980392, 1.0, 0.9490196078, 0.0, + 0.7607843137254902, 1.0, 0.9490196078, 0.0, 0.7647058823529411, 1.0, 0.9490196078, 0.0, + 0.7686274509803922, 1.0, 0.9490196078, 0.0, 0.7725490196078432, 1.0, 0.9490196078, 0.0, + 0.7764705882352941, 1.0, 0.9490196078, 0.0, 0.7803921568627451, 1.0, 0.9490196078, 0.0, + 0.7843137254901961, 1.0, 0.9490196078, 0.0, 0.788235294117647, 1.0, 0.9490196078, 0.0, + 0.792156862745098, 1.0, 0.9490196078, 0.0, 0.796078431372549, 1.0, 0.9490196078, 0.0, 0.8, + 1.0, 0.9490196078, 0.0, 0.803921568627451, 1.0, 0.9490196078, 0.0, 0.807843137254902, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8117647058823529, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8156862745098039, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8196078431372549, 0.9490196078, 0.6509803922, 0.2509803922, 0.8235294117647058, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8274509803921568, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8313725490196079, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8352941176470589, 0.9490196078, 0.6509803922, 0.2509803922, 0.8392156862745098, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8431372549019608, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8470588235294118, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8509803921568627, 0.9490196078, 0.6509803922, 0.2509803922, 0.8549019607843137, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8588235294117647, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8627450980392157, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8666666666666667, 0.9490196078, 0.6509803922, 0.2509803922, 0.8705882352941177, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8745098039215686, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8784313725490196, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8823529411764706, 0.9490196078, 0.6509803922, 0.2509803922, 0.8862745098039215, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8901960784313725, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8941176470588236, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8980392156862745, 0.9490196078, 0.6509803922, 0.2509803922, 0.9019607843137255, + 0.9490196078, 0.6509803922, 0.2509803922, 0.9058823529411765, 0.9490196078, 0.6509803922, + 0.2509803922, 0.9098039215686274, 1.0, 0.0, 0.0, 0.9137254901960784, 1.0, 0.0, 0.0, + 0.9176470588235294, 1.0, 0.0, 0.0, 0.9215686274509803, 1.0, 0.0, 0.0, 0.9254901960784314, 1.0, + 0.0, 0.0, 0.9294117647058824, 1.0, 0.0, 0.0, 0.9333333333333333, 1.0, 0.0, 0.0, + 0.9372549019607843, 1.0, 0.0, 0.0, 0.9411764705882354, 1.0, 0.0, 0.0, 0.9450980392156864, 1.0, + 0.0, 0.0, 0.9490196078431372, 1.0, 0.0, 0.0, 0.9529411764705882, 1.0, 0.0, 0.0, + 0.9568627450980394, 1.0, 0.0, 0.0, 0.9607843137254903, 1.0, 0.0, 0.0, 0.9647058823529413, 1.0, + 0.0, 0.0, 0.9686274509803922, 1.0, 0.0, 0.0, 0.9725490196078431, 1.0, 0.0, 0.0, + 0.9764705882352941, 1.0, 0.0, 0.0, 0.9803921568627451, 1.0, 0.0, 0.0, 0.984313725490196, 1.0, + 0.0, 0.0, 0.9882352941176471, 1.0, 0.0, 0.0, 0.9921568627450981, 1.0, 0.0, 0.0, + 0.996078431372549, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, ], }, { ColorSpace: 'RGB', Name: 'ge_256', RGBPoints: [ - 0.0, - 0.0039215686, - 0.0078431373, - 0.0078431373, - 0.00392156862745098, - 0.0039215686, - 0.0078431373, - 0.0078431373, - 0.00784313725490196, - 0.0039215686, - 0.0078431373, - 0.0117647059, - 0.011764705882352941, - 0.0039215686, - 0.0117647059, - 0.0156862745, - 0.01568627450980392, - 0.0039215686, - 0.0117647059, - 0.0196078431, - 0.0196078431372549, - 0.0039215686, - 0.0156862745, - 0.0235294118, - 0.023529411764705882, - 0.0039215686, - 0.0156862745, - 0.0274509804, - 0.027450980392156862, - 0.0039215686, - 0.0196078431, - 0.031372549, - 0.03137254901960784, - 0.0039215686, - 0.0196078431, - 0.0352941176, - 0.03529411764705882, - 0.0039215686, - 0.0235294118, - 0.0392156863, - 0.0392156862745098, - 0.0039215686, - 0.0235294118, - 0.0431372549, - 0.043137254901960784, - 0.0039215686, - 0.0274509804, - 0.0470588235, - 0.047058823529411764, - 0.0039215686, - 0.0274509804, - 0.0509803922, - 0.050980392156862744, - 0.0039215686, - 0.031372549, - 0.0549019608, - 0.054901960784313725, - 0.0039215686, - 0.031372549, - 0.0588235294, - 0.05882352941176471, - 0.0039215686, - 0.0352941176, - 0.062745098, - 0.06274509803921569, - 0.0039215686, - 0.0352941176, - 0.0666666667, - 0.06666666666666667, - 0.0039215686, - 0.0392156863, - 0.0705882353, - 0.07058823529411765, - 0.0039215686, - 0.0392156863, - 0.0745098039, - 0.07450980392156863, - 0.0039215686, - 0.0431372549, - 0.0784313725, - 0.0784313725490196, - 0.0039215686, - 0.0431372549, - 0.0823529412, - 0.08235294117647059, - 0.0039215686, - 0.0470588235, - 0.0862745098, - 0.08627450980392157, - 0.0039215686, - 0.0470588235, - 0.0901960784, - 0.09019607843137255, - 0.0039215686, - 0.0509803922, - 0.0941176471, - 0.09411764705882353, - 0.0039215686, - 0.0509803922, - 0.0980392157, - 0.09803921568627451, - 0.0039215686, - 0.0549019608, - 0.1019607843, - 0.10196078431372549, - 0.0039215686, - 0.0549019608, - 0.1058823529, - 0.10588235294117647, - 0.0039215686, - 0.0588235294, - 0.1098039216, - 0.10980392156862745, - 0.0039215686, - 0.0588235294, - 0.1137254902, - 0.11372549019607843, - 0.0039215686, - 0.062745098, - 0.1176470588, - 0.11764705882352942, - 0.0039215686, - 0.062745098, - 0.1215686275, - 0.12156862745098039, - 0.0039215686, - 0.0666666667, - 0.1254901961, - 0.12549019607843137, - 0.0039215686, - 0.0666666667, - 0.1294117647, - 0.12941176470588237, - 0.0039215686, - 0.0705882353, - 0.1333333333, - 0.13333333333333333, - 0.0039215686, - 0.0705882353, - 0.137254902, - 0.13725490196078433, - 0.0039215686, - 0.0745098039, - 0.1411764706, - 0.1411764705882353, - 0.0039215686, - 0.0745098039, - 0.1450980392, - 0.1450980392156863, - 0.0039215686, - 0.0784313725, - 0.1490196078, - 0.14901960784313725, - 0.0039215686, - 0.0784313725, - 0.1529411765, - 0.15294117647058825, - 0.0039215686, - 0.0823529412, - 0.1568627451, - 0.1568627450980392, - 0.0039215686, - 0.0823529412, - 0.1607843137, - 0.1607843137254902, - 0.0039215686, - 0.0862745098, - 0.1647058824, - 0.16470588235294117, - 0.0039215686, - 0.0862745098, - 0.168627451, - 0.16862745098039217, - 0.0039215686, - 0.0901960784, - 0.1725490196, - 0.17254901960784313, - 0.0039215686, - 0.0901960784, - 0.1764705882, - 0.17647058823529413, - 0.0039215686, - 0.0941176471, - 0.1803921569, - 0.1803921568627451, - 0.0039215686, - 0.0941176471, - 0.1843137255, - 0.1843137254901961, - 0.0039215686, - 0.0980392157, - 0.1882352941, - 0.18823529411764706, - 0.0039215686, - 0.0980392157, - 0.1921568627, - 0.19215686274509805, - 0.0039215686, - 0.1019607843, - 0.1960784314, - 0.19607843137254902, - 0.0039215686, - 0.1019607843, - 0.2, - 0.2, - 0.0039215686, - 0.1058823529, - 0.2039215686, - 0.20392156862745098, - 0.0039215686, - 0.1058823529, - 0.2078431373, - 0.20784313725490197, - 0.0039215686, - 0.1098039216, - 0.2117647059, - 0.21176470588235294, - 0.0039215686, - 0.1098039216, - 0.2156862745, - 0.21568627450980393, - 0.0039215686, - 0.1137254902, - 0.2196078431, - 0.2196078431372549, - 0.0039215686, - 0.1137254902, - 0.2235294118, - 0.2235294117647059, - 0.0039215686, - 0.1176470588, - 0.2274509804, - 0.22745098039215686, - 0.0039215686, - 0.1176470588, - 0.231372549, - 0.23137254901960785, - 0.0039215686, - 0.1215686275, - 0.2352941176, - 0.23529411764705885, - 0.0039215686, - 0.1215686275, - 0.2392156863, - 0.23921568627450984, - 0.0039215686, - 0.1254901961, - 0.2431372549, - 0.24313725490196078, - 0.0039215686, - 0.1254901961, - 0.2470588235, - 0.24705882352941178, - 0.0039215686, - 0.1294117647, - 0.2509803922, - 0.25098039215686274, - 0.0039215686, - 0.1294117647, - 0.2509803922, - 0.2549019607843137, - 0.0078431373, - 0.1254901961, - 0.2549019608, - 0.25882352941176473, - 0.0156862745, - 0.1254901961, - 0.2588235294, - 0.2627450980392157, - 0.0235294118, - 0.1215686275, - 0.262745098, - 0.26666666666666666, - 0.031372549, - 0.1215686275, - 0.2666666667, - 0.27058823529411763, - 0.0392156863, - 0.1176470588, - 0.2705882353, - 0.27450980392156865, - 0.0470588235, - 0.1176470588, - 0.2745098039, - 0.2784313725490196, - 0.0549019608, - 0.1137254902, - 0.2784313725, - 0.2823529411764706, - 0.062745098, - 0.1137254902, - 0.2823529412, - 0.28627450980392155, - 0.0705882353, - 0.1098039216, - 0.2862745098, - 0.2901960784313726, - 0.0784313725, - 0.1098039216, - 0.2901960784, - 0.29411764705882354, - 0.0862745098, - 0.1058823529, - 0.2941176471, - 0.2980392156862745, - 0.0941176471, - 0.1058823529, - 0.2980392157, - 0.30196078431372547, - 0.1019607843, - 0.1019607843, - 0.3019607843, - 0.3058823529411765, - 0.1098039216, - 0.1019607843, - 0.3058823529, - 0.30980392156862746, - 0.1176470588, - 0.0980392157, - 0.3098039216, - 0.3137254901960784, - 0.1254901961, - 0.0980392157, - 0.3137254902, - 0.3176470588235294, - 0.1333333333, - 0.0941176471, - 0.3176470588, - 0.3215686274509804, - 0.1411764706, - 0.0941176471, - 0.3215686275, - 0.3254901960784314, - 0.1490196078, - 0.0901960784, - 0.3254901961, - 0.32941176470588235, - 0.1568627451, - 0.0901960784, - 0.3294117647, - 0.3333333333333333, - 0.1647058824, - 0.0862745098, - 0.3333333333, - 0.33725490196078434, - 0.1725490196, - 0.0862745098, - 0.337254902, - 0.3411764705882353, - 0.1803921569, - 0.0823529412, - 0.3411764706, - 0.34509803921568627, - 0.1882352941, - 0.0823529412, - 0.3450980392, - 0.34901960784313724, - 0.1960784314, - 0.0784313725, - 0.3490196078, - 0.35294117647058826, - 0.2039215686, - 0.0784313725, - 0.3529411765, - 0.3568627450980392, - 0.2117647059, - 0.0745098039, - 0.3568627451, - 0.3607843137254902, - 0.2196078431, - 0.0745098039, - 0.3607843137, - 0.36470588235294116, - 0.2274509804, - 0.0705882353, - 0.3647058824, - 0.3686274509803922, - 0.2352941176, - 0.0705882353, - 0.368627451, - 0.37254901960784315, - 0.2431372549, - 0.0666666667, - 0.3725490196, - 0.3764705882352941, - 0.2509803922, - 0.0666666667, - 0.3764705882, - 0.3803921568627451, - 0.2549019608, - 0.062745098, - 0.3803921569, - 0.3843137254901961, - 0.262745098, - 0.062745098, - 0.3843137255, - 0.38823529411764707, - 0.2705882353, - 0.0588235294, - 0.3882352941, - 0.39215686274509803, - 0.2784313725, - 0.0588235294, - 0.3921568627, - 0.396078431372549, - 0.2862745098, - 0.0549019608, - 0.3960784314, - 0.4, - 0.2941176471, - 0.0549019608, - 0.4, - 0.403921568627451, - 0.3019607843, - 0.0509803922, - 0.4039215686, - 0.40784313725490196, - 0.3098039216, - 0.0509803922, - 0.4078431373, - 0.4117647058823529, - 0.3176470588, - 0.0470588235, - 0.4117647059, - 0.41568627450980394, - 0.3254901961, - 0.0470588235, - 0.4156862745, - 0.4196078431372549, - 0.3333333333, - 0.0431372549, - 0.4196078431, - 0.4235294117647059, - 0.3411764706, - 0.0431372549, - 0.4235294118, - 0.42745098039215684, - 0.3490196078, - 0.0392156863, - 0.4274509804, - 0.43137254901960786, - 0.3568627451, - 0.0392156863, - 0.431372549, - 0.43529411764705883, - 0.3647058824, - 0.0352941176, - 0.4352941176, - 0.4392156862745098, - 0.3725490196, - 0.0352941176, - 0.4392156863, - 0.44313725490196076, - 0.3803921569, - 0.031372549, - 0.4431372549, - 0.4470588235294118, - 0.3882352941, - 0.031372549, - 0.4470588235, - 0.45098039215686275, - 0.3960784314, - 0.0274509804, - 0.4509803922, - 0.4549019607843137, - 0.4039215686, - 0.0274509804, - 0.4549019608, - 0.4588235294117647, - 0.4117647059, - 0.0235294118, - 0.4588235294, - 0.4627450980392157, - 0.4196078431, - 0.0235294118, - 0.462745098, - 0.4666666666666667, - 0.4274509804, - 0.0196078431, - 0.4666666667, - 0.4705882352941177, - 0.4352941176, - 0.0196078431, - 0.4705882353, - 0.4745098039215686, - 0.4431372549, - 0.0156862745, - 0.4745098039, - 0.4784313725490197, - 0.4509803922, - 0.0156862745, - 0.4784313725, - 0.48235294117647065, - 0.4588235294, - 0.0117647059, - 0.4823529412, - 0.48627450980392156, - 0.4666666667, - 0.0117647059, - 0.4862745098, - 0.49019607843137253, - 0.4745098039, - 0.0078431373, - 0.4901960784, - 0.49411764705882355, - 0.4823529412, - 0.0078431373, - 0.4941176471, - 0.4980392156862745, - 0.4901960784, - 0.0039215686, - 0.4980392157, - 0.5019607843137255, - 0.4980392157, - 0.0117647059, - 0.4980392157, - 0.5058823529411764, - 0.5058823529, - 0.0156862745, - 0.4901960784, - 0.5098039215686274, - 0.5137254902, - 0.0235294118, - 0.4823529412, - 0.5137254901960784, - 0.5215686275, - 0.0274509804, - 0.4745098039, - 0.5176470588235295, - 0.5294117647, - 0.0352941176, - 0.4666666667, - 0.5215686274509804, - 0.537254902, - 0.0392156863, - 0.4588235294, - 0.5254901960784314, - 0.5450980392, - 0.0470588235, - 0.4509803922, - 0.5294117647058824, - 0.5529411765, - 0.0509803922, - 0.4431372549, - 0.5333333333333333, - 0.5607843137, - 0.0588235294, - 0.4352941176, - 0.5372549019607843, - 0.568627451, - 0.062745098, - 0.4274509804, - 0.5411764705882353, - 0.5764705882, - 0.0705882353, - 0.4196078431, - 0.5450980392156862, - 0.5843137255, - 0.0745098039, - 0.4117647059, - 0.5490196078431373, - 0.5921568627, - 0.0823529412, - 0.4039215686, - 0.5529411764705883, - 0.6, - 0.0862745098, - 0.3960784314, - 0.5568627450980392, - 0.6078431373, - 0.0941176471, - 0.3882352941, - 0.5607843137254902, - 0.6156862745, - 0.0980392157, - 0.3803921569, - 0.5647058823529412, - 0.6235294118, - 0.1058823529, - 0.3725490196, - 0.5686274509803921, - 0.631372549, - 0.1098039216, - 0.3647058824, - 0.5725490196078431, - 0.6392156863, - 0.1176470588, - 0.3568627451, - 0.5764705882352941, - 0.6470588235, - 0.1215686275, - 0.3490196078, - 0.5803921568627451, - 0.6549019608, - 0.1294117647, - 0.3411764706, - 0.5843137254901961, - 0.662745098, - 0.1333333333, - 0.3333333333, - 0.5882352941176471, - 0.6705882353, - 0.1411764706, - 0.3254901961, - 0.592156862745098, - 0.6784313725, - 0.1450980392, - 0.3176470588, - 0.596078431372549, - 0.6862745098, - 0.1529411765, - 0.3098039216, - 0.6, - 0.6941176471, - 0.1568627451, - 0.3019607843, - 0.6039215686274509, - 0.7019607843, - 0.1647058824, - 0.2941176471, - 0.6078431372549019, - 0.7098039216, - 0.168627451, - 0.2862745098, - 0.611764705882353, - 0.7176470588, - 0.1764705882, - 0.2784313725, - 0.615686274509804, - 0.7254901961, - 0.1803921569, - 0.2705882353, - 0.6196078431372549, - 0.7333333333, - 0.1882352941, - 0.262745098, - 0.6235294117647059, - 0.7411764706, - 0.1921568627, - 0.2549019608, - 0.6274509803921569, - 0.7490196078, - 0.2, - 0.2509803922, - 0.6313725490196078, - 0.7529411765, - 0.2039215686, - 0.2431372549, - 0.6352941176470588, - 0.7607843137, - 0.2117647059, - 0.2352941176, - 0.6392156862745098, - 0.768627451, - 0.2156862745, - 0.2274509804, - 0.6431372549019608, - 0.7764705882, - 0.2235294118, - 0.2196078431, - 0.6470588235294118, - 0.7843137255, - 0.2274509804, - 0.2117647059, - 0.6509803921568628, - 0.7921568627, - 0.2352941176, - 0.2039215686, - 0.6549019607843137, - 0.8, - 0.2392156863, - 0.1960784314, - 0.6588235294117647, - 0.8078431373, - 0.2470588235, - 0.1882352941, - 0.6627450980392157, - 0.8156862745, - 0.2509803922, - 0.1803921569, - 0.6666666666666666, - 0.8235294118, - 0.2549019608, - 0.1725490196, - 0.6705882352941176, - 0.831372549, - 0.2588235294, - 0.1647058824, - 0.6745098039215687, - 0.8392156863, - 0.2666666667, - 0.1568627451, - 0.6784313725490196, - 0.8470588235, - 0.2705882353, - 0.1490196078, - 0.6823529411764706, - 0.8549019608, - 0.2784313725, - 0.1411764706, - 0.6862745098039216, - 0.862745098, - 0.2823529412, - 0.1333333333, - 0.6901960784313725, - 0.8705882353, - 0.2901960784, - 0.1254901961, - 0.6941176470588235, - 0.8784313725, - 0.2941176471, - 0.1176470588, - 0.6980392156862745, - 0.8862745098, - 0.3019607843, - 0.1098039216, - 0.7019607843137254, - 0.8941176471, - 0.3058823529, - 0.1019607843, - 0.7058823529411765, - 0.9019607843, - 0.3137254902, - 0.0941176471, - 0.7098039215686275, - 0.9098039216, - 0.3176470588, - 0.0862745098, - 0.7137254901960784, - 0.9176470588, - 0.3254901961, - 0.0784313725, - 0.7176470588235294, - 0.9254901961, - 0.3294117647, - 0.0705882353, - 0.7215686274509804, - 0.9333333333, - 0.337254902, - 0.062745098, - 0.7254901960784313, - 0.9411764706, - 0.3411764706, - 0.0549019608, - 0.7294117647058823, - 0.9490196078, - 0.3490196078, - 0.0470588235, - 0.7333333333333333, - 0.9568627451, - 0.3529411765, - 0.0392156863, - 0.7372549019607844, - 0.9647058824, - 0.3607843137, - 0.031372549, - 0.7411764705882353, - 0.9725490196, - 0.3647058824, - 0.0235294118, - 0.7450980392156863, - 0.9803921569, - 0.3725490196, - 0.0156862745, - 0.7490196078431373, - 0.9882352941, - 0.3725490196, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.3843137255, - 0.0156862745, - 0.7568627450980392, - 0.9960784314, - 0.3921568627, - 0.031372549, - 0.7607843137254902, - 0.9960784314, - 0.4039215686, - 0.0470588235, - 0.7647058823529411, - 0.9960784314, - 0.4117647059, - 0.062745098, - 0.7686274509803922, - 0.9960784314, - 0.4235294118, - 0.0784313725, - 0.7725490196078432, - 0.9960784314, - 0.431372549, - 0.0941176471, - 0.7764705882352941, - 0.9960784314, - 0.4431372549, - 0.1098039216, - 0.7803921568627451, - 0.9960784314, - 0.4509803922, - 0.1254901961, - 0.7843137254901961, - 0.9960784314, - 0.462745098, - 0.1411764706, - 0.788235294117647, - 0.9960784314, - 0.4705882353, - 0.1568627451, - 0.792156862745098, - 0.9960784314, - 0.4823529412, - 0.1725490196, - 0.796078431372549, - 0.9960784314, - 0.4901960784, - 0.1882352941, - 0.8, - 0.9960784314, - 0.5019607843, - 0.2039215686, - 0.803921568627451, - 0.9960784314, - 0.5098039216, - 0.2196078431, - 0.807843137254902, - 0.9960784314, - 0.5215686275, - 0.2352941176, - 0.8117647058823529, - 0.9960784314, - 0.5294117647, - 0.2509803922, - 0.8156862745098039, - 0.9960784314, - 0.5411764706, - 0.262745098, - 0.8196078431372549, - 0.9960784314, - 0.5490196078, - 0.2784313725, - 0.8235294117647058, - 0.9960784314, - 0.5607843137, - 0.2941176471, - 0.8274509803921568, - 0.9960784314, - 0.568627451, - 0.3098039216, - 0.8313725490196079, - 0.9960784314, - 0.5803921569, - 0.3254901961, - 0.8352941176470589, - 0.9960784314, - 0.5882352941, - 0.3411764706, - 0.8392156862745098, - 0.9960784314, - 0.6, - 0.3568627451, - 0.8431372549019608, - 0.9960784314, - 0.6078431373, - 0.3725490196, - 0.8470588235294118, - 0.9960784314, - 0.6196078431, - 0.3882352941, - 0.8509803921568627, - 0.9960784314, - 0.6274509804, - 0.4039215686, - 0.8549019607843137, - 0.9960784314, - 0.6392156863, - 0.4196078431, - 0.8588235294117647, - 0.9960784314, - 0.6470588235, - 0.4352941176, - 0.8627450980392157, - 0.9960784314, - 0.6588235294, - 0.4509803922, - 0.8666666666666667, - 0.9960784314, - 0.6666666667, - 0.4666666667, - 0.8705882352941177, - 0.9960784314, - 0.6784313725, - 0.4823529412, - 0.8745098039215686, - 0.9960784314, - 0.6862745098, - 0.4980392157, - 0.8784313725490196, - 0.9960784314, - 0.6980392157, - 0.5137254902, - 0.8823529411764706, - 0.9960784314, - 0.7058823529, - 0.5294117647, - 0.8862745098039215, - 0.9960784314, - 0.7176470588, - 0.5450980392, - 0.8901960784313725, - 0.9960784314, - 0.7254901961, - 0.5607843137, - 0.8941176470588236, - 0.9960784314, - 0.737254902, - 0.5764705882, - 0.8980392156862745, - 0.9960784314, - 0.7450980392, - 0.5921568627, - 0.9019607843137255, - 0.9960784314, - 0.7529411765, - 0.6078431373, - 0.9058823529411765, - 0.9960784314, - 0.7607843137, - 0.6235294118, - 0.9098039215686274, - 0.9960784314, - 0.7725490196, - 0.6392156863, - 0.9137254901960784, - 0.9960784314, - 0.7803921569, - 0.6549019608, - 0.9176470588235294, - 0.9960784314, - 0.7921568627, - 0.6705882353, - 0.9215686274509803, - 0.9960784314, - 0.8, - 0.6862745098, - 0.9254901960784314, - 0.9960784314, - 0.8117647059, - 0.7019607843, - 0.9294117647058824, - 0.9960784314, - 0.8196078431, - 0.7176470588, - 0.9333333333333333, - 0.9960784314, - 0.831372549, - 0.7333333333, - 0.9372549019607843, - 0.9960784314, - 0.8392156863, - 0.7490196078, - 0.9411764705882354, - 0.9960784314, - 0.8509803922, - 0.7607843137, - 0.9450980392156864, - 0.9960784314, - 0.8588235294, - 0.7764705882, - 0.9490196078431372, - 0.9960784314, - 0.8705882353, - 0.7921568627, - 0.9529411764705882, - 0.9960784314, - 0.8784313725, - 0.8078431373, - 0.9568627450980394, - 0.9960784314, - 0.8901960784, - 0.8235294118, - 0.9607843137254903, - 0.9960784314, - 0.8980392157, - 0.8392156863, - 0.9647058823529413, - 0.9960784314, - 0.9098039216, - 0.8549019608, - 0.9686274509803922, - 0.9960784314, - 0.9176470588, - 0.8705882353, - 0.9725490196078431, - 0.9960784314, - 0.9294117647, - 0.8862745098, - 0.9764705882352941, - 0.9960784314, - 0.937254902, - 0.9019607843, - 0.9803921568627451, - 0.9960784314, - 0.9490196078, - 0.9176470588, - 0.984313725490196, - 0.9960784314, - 0.9568627451, - 0.9333333333, - 0.9882352941176471, - 0.9960784314, - 0.968627451, - 0.9490196078, - 0.9921568627450981, - 0.9960784314, - 0.9764705882, - 0.9647058824, - 0.996078431372549, - 0.9960784314, - 0.9882352941, - 0.9803921569, - 1.0, - 0.9960784314, - 0.9882352941, - 0.9803921569, + 0.0, 0.0039215686, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0039215686, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0039215686, 0.0078431373, 0.0117647059, + 0.011764705882352941, 0.0039215686, 0.0117647059, 0.0156862745, 0.01568627450980392, + 0.0039215686, 0.0117647059, 0.0196078431, 0.0196078431372549, 0.0039215686, 0.0156862745, + 0.0235294118, 0.023529411764705882, 0.0039215686, 0.0156862745, 0.0274509804, + 0.027450980392156862, 0.0039215686, 0.0196078431, 0.031372549, 0.03137254901960784, + 0.0039215686, 0.0196078431, 0.0352941176, 0.03529411764705882, 0.0039215686, 0.0235294118, + 0.0392156863, 0.0392156862745098, 0.0039215686, 0.0235294118, 0.0431372549, + 0.043137254901960784, 0.0039215686, 0.0274509804, 0.0470588235, 0.047058823529411764, + 0.0039215686, 0.0274509804, 0.0509803922, 0.050980392156862744, 0.0039215686, 0.031372549, + 0.0549019608, 0.054901960784313725, 0.0039215686, 0.031372549, 0.0588235294, + 0.05882352941176471, 0.0039215686, 0.0352941176, 0.062745098, 0.06274509803921569, + 0.0039215686, 0.0352941176, 0.0666666667, 0.06666666666666667, 0.0039215686, 0.0392156863, + 0.0705882353, 0.07058823529411765, 0.0039215686, 0.0392156863, 0.0745098039, + 0.07450980392156863, 0.0039215686, 0.0431372549, 0.0784313725, 0.0784313725490196, + 0.0039215686, 0.0431372549, 0.0823529412, 0.08235294117647059, 0.0039215686, 0.0470588235, + 0.0862745098, 0.08627450980392157, 0.0039215686, 0.0470588235, 0.0901960784, + 0.09019607843137255, 0.0039215686, 0.0509803922, 0.0941176471, 0.09411764705882353, + 0.0039215686, 0.0509803922, 0.0980392157, 0.09803921568627451, 0.0039215686, 0.0549019608, + 0.1019607843, 0.10196078431372549, 0.0039215686, 0.0549019608, 0.1058823529, + 0.10588235294117647, 0.0039215686, 0.0588235294, 0.1098039216, 0.10980392156862745, + 0.0039215686, 0.0588235294, 0.1137254902, 0.11372549019607843, 0.0039215686, 0.062745098, + 0.1176470588, 0.11764705882352942, 0.0039215686, 0.062745098, 0.1215686275, + 0.12156862745098039, 0.0039215686, 0.0666666667, 0.1254901961, 0.12549019607843137, + 0.0039215686, 0.0666666667, 0.1294117647, 0.12941176470588237, 0.0039215686, 0.0705882353, + 0.1333333333, 0.13333333333333333, 0.0039215686, 0.0705882353, 0.137254902, + 0.13725490196078433, 0.0039215686, 0.0745098039, 0.1411764706, 0.1411764705882353, + 0.0039215686, 0.0745098039, 0.1450980392, 0.1450980392156863, 0.0039215686, 0.0784313725, + 0.1490196078, 0.14901960784313725, 0.0039215686, 0.0784313725, 0.1529411765, + 0.15294117647058825, 0.0039215686, 0.0823529412, 0.1568627451, 0.1568627450980392, + 0.0039215686, 0.0823529412, 0.1607843137, 0.1607843137254902, 0.0039215686, 0.0862745098, + 0.1647058824, 0.16470588235294117, 0.0039215686, 0.0862745098, 0.168627451, + 0.16862745098039217, 0.0039215686, 0.0901960784, 0.1725490196, 0.17254901960784313, + 0.0039215686, 0.0901960784, 0.1764705882, 0.17647058823529413, 0.0039215686, 0.0941176471, + 0.1803921569, 0.1803921568627451, 0.0039215686, 0.0941176471, 0.1843137255, + 0.1843137254901961, 0.0039215686, 0.0980392157, 0.1882352941, 0.18823529411764706, + 0.0039215686, 0.0980392157, 0.1921568627, 0.19215686274509805, 0.0039215686, 0.1019607843, + 0.1960784314, 0.19607843137254902, 0.0039215686, 0.1019607843, 0.2, 0.2, 0.0039215686, + 0.1058823529, 0.2039215686, 0.20392156862745098, 0.0039215686, 0.1058823529, 0.2078431373, + 0.20784313725490197, 0.0039215686, 0.1098039216, 0.2117647059, 0.21176470588235294, + 0.0039215686, 0.1098039216, 0.2156862745, 0.21568627450980393, 0.0039215686, 0.1137254902, + 0.2196078431, 0.2196078431372549, 0.0039215686, 0.1137254902, 0.2235294118, + 0.2235294117647059, 0.0039215686, 0.1176470588, 0.2274509804, 0.22745098039215686, + 0.0039215686, 0.1176470588, 0.231372549, 0.23137254901960785, 0.0039215686, 0.1215686275, + 0.2352941176, 0.23529411764705885, 0.0039215686, 0.1215686275, 0.2392156863, + 0.23921568627450984, 0.0039215686, 0.1254901961, 0.2431372549, 0.24313725490196078, + 0.0039215686, 0.1254901961, 0.2470588235, 0.24705882352941178, 0.0039215686, 0.1294117647, + 0.2509803922, 0.25098039215686274, 0.0039215686, 0.1294117647, 0.2509803922, + 0.2549019607843137, 0.0078431373, 0.1254901961, 0.2549019608, 0.25882352941176473, + 0.0156862745, 0.1254901961, 0.2588235294, 0.2627450980392157, 0.0235294118, 0.1215686275, + 0.262745098, 0.26666666666666666, 0.031372549, 0.1215686275, 0.2666666667, + 0.27058823529411763, 0.0392156863, 0.1176470588, 0.2705882353, 0.27450980392156865, + 0.0470588235, 0.1176470588, 0.2745098039, 0.2784313725490196, 0.0549019608, 0.1137254902, + 0.2784313725, 0.2823529411764706, 0.062745098, 0.1137254902, 0.2823529412, + 0.28627450980392155, 0.0705882353, 0.1098039216, 0.2862745098, 0.2901960784313726, + 0.0784313725, 0.1098039216, 0.2901960784, 0.29411764705882354, 0.0862745098, 0.1058823529, + 0.2941176471, 0.2980392156862745, 0.0941176471, 0.1058823529, 0.2980392157, + 0.30196078431372547, 0.1019607843, 0.1019607843, 0.3019607843, 0.3058823529411765, + 0.1098039216, 0.1019607843, 0.3058823529, 0.30980392156862746, 0.1176470588, 0.0980392157, + 0.3098039216, 0.3137254901960784, 0.1254901961, 0.0980392157, 0.3137254902, + 0.3176470588235294, 0.1333333333, 0.0941176471, 0.3176470588, 0.3215686274509804, + 0.1411764706, 0.0941176471, 0.3215686275, 0.3254901960784314, 0.1490196078, 0.0901960784, + 0.3254901961, 0.32941176470588235, 0.1568627451, 0.0901960784, 0.3294117647, + 0.3333333333333333, 0.1647058824, 0.0862745098, 0.3333333333, 0.33725490196078434, + 0.1725490196, 0.0862745098, 0.337254902, 0.3411764705882353, 0.1803921569, 0.0823529412, + 0.3411764706, 0.34509803921568627, 0.1882352941, 0.0823529412, 0.3450980392, + 0.34901960784313724, 0.1960784314, 0.0784313725, 0.3490196078, 0.35294117647058826, + 0.2039215686, 0.0784313725, 0.3529411765, 0.3568627450980392, 0.2117647059, 0.0745098039, + 0.3568627451, 0.3607843137254902, 0.2196078431, 0.0745098039, 0.3607843137, + 0.36470588235294116, 0.2274509804, 0.0705882353, 0.3647058824, 0.3686274509803922, + 0.2352941176, 0.0705882353, 0.368627451, 0.37254901960784315, 0.2431372549, 0.0666666667, + 0.3725490196, 0.3764705882352941, 0.2509803922, 0.0666666667, 0.3764705882, + 0.3803921568627451, 0.2549019608, 0.062745098, 0.3803921569, 0.3843137254901961, 0.262745098, + 0.062745098, 0.3843137255, 0.38823529411764707, 0.2705882353, 0.0588235294, 0.3882352941, + 0.39215686274509803, 0.2784313725, 0.0588235294, 0.3921568627, 0.396078431372549, + 0.2862745098, 0.0549019608, 0.3960784314, 0.4, 0.2941176471, 0.0549019608, 0.4, + 0.403921568627451, 0.3019607843, 0.0509803922, 0.4039215686, 0.40784313725490196, + 0.3098039216, 0.0509803922, 0.4078431373, 0.4117647058823529, 0.3176470588, 0.0470588235, + 0.4117647059, 0.41568627450980394, 0.3254901961, 0.0470588235, 0.4156862745, + 0.4196078431372549, 0.3333333333, 0.0431372549, 0.4196078431, 0.4235294117647059, + 0.3411764706, 0.0431372549, 0.4235294118, 0.42745098039215684, 0.3490196078, 0.0392156863, + 0.4274509804, 0.43137254901960786, 0.3568627451, 0.0392156863, 0.431372549, + 0.43529411764705883, 0.3647058824, 0.0352941176, 0.4352941176, 0.4392156862745098, + 0.3725490196, 0.0352941176, 0.4392156863, 0.44313725490196076, 0.3803921569, 0.031372549, + 0.4431372549, 0.4470588235294118, 0.3882352941, 0.031372549, 0.4470588235, + 0.45098039215686275, 0.3960784314, 0.0274509804, 0.4509803922, 0.4549019607843137, + 0.4039215686, 0.0274509804, 0.4549019608, 0.4588235294117647, 0.4117647059, 0.0235294118, + 0.4588235294, 0.4627450980392157, 0.4196078431, 0.0235294118, 0.462745098, 0.4666666666666667, + 0.4274509804, 0.0196078431, 0.4666666667, 0.4705882352941177, 0.4352941176, 0.0196078431, + 0.4705882353, 0.4745098039215686, 0.4431372549, 0.0156862745, 0.4745098039, + 0.4784313725490197, 0.4509803922, 0.0156862745, 0.4784313725, 0.48235294117647065, + 0.4588235294, 0.0117647059, 0.4823529412, 0.48627450980392156, 0.4666666667, 0.0117647059, + 0.4862745098, 0.49019607843137253, 0.4745098039, 0.0078431373, 0.4901960784, + 0.49411764705882355, 0.4823529412, 0.0078431373, 0.4941176471, 0.4980392156862745, + 0.4901960784, 0.0039215686, 0.4980392157, 0.5019607843137255, 0.4980392157, 0.0117647059, + 0.4980392157, 0.5058823529411764, 0.5058823529, 0.0156862745, 0.4901960784, + 0.5098039215686274, 0.5137254902, 0.0235294118, 0.4823529412, 0.5137254901960784, + 0.5215686275, 0.0274509804, 0.4745098039, 0.5176470588235295, 0.5294117647, 0.0352941176, + 0.4666666667, 0.5215686274509804, 0.537254902, 0.0392156863, 0.4588235294, 0.5254901960784314, + 0.5450980392, 0.0470588235, 0.4509803922, 0.5294117647058824, 0.5529411765, 0.0509803922, + 0.4431372549, 0.5333333333333333, 0.5607843137, 0.0588235294, 0.4352941176, + 0.5372549019607843, 0.568627451, 0.062745098, 0.4274509804, 0.5411764705882353, 0.5764705882, + 0.0705882353, 0.4196078431, 0.5450980392156862, 0.5843137255, 0.0745098039, 0.4117647059, + 0.5490196078431373, 0.5921568627, 0.0823529412, 0.4039215686, 0.5529411764705883, 0.6, + 0.0862745098, 0.3960784314, 0.5568627450980392, 0.6078431373, 0.0941176471, 0.3882352941, + 0.5607843137254902, 0.6156862745, 0.0980392157, 0.3803921569, 0.5647058823529412, + 0.6235294118, 0.1058823529, 0.3725490196, 0.5686274509803921, 0.631372549, 0.1098039216, + 0.3647058824, 0.5725490196078431, 0.6392156863, 0.1176470588, 0.3568627451, + 0.5764705882352941, 0.6470588235, 0.1215686275, 0.3490196078, 0.5803921568627451, + 0.6549019608, 0.1294117647, 0.3411764706, 0.5843137254901961, 0.662745098, 0.1333333333, + 0.3333333333, 0.5882352941176471, 0.6705882353, 0.1411764706, 0.3254901961, 0.592156862745098, + 0.6784313725, 0.1450980392, 0.3176470588, 0.596078431372549, 0.6862745098, 0.1529411765, + 0.3098039216, 0.6, 0.6941176471, 0.1568627451, 0.3019607843, 0.6039215686274509, 0.7019607843, + 0.1647058824, 0.2941176471, 0.6078431372549019, 0.7098039216, 0.168627451, 0.2862745098, + 0.611764705882353, 0.7176470588, 0.1764705882, 0.2784313725, 0.615686274509804, 0.7254901961, + 0.1803921569, 0.2705882353, 0.6196078431372549, 0.7333333333, 0.1882352941, 0.262745098, + 0.6235294117647059, 0.7411764706, 0.1921568627, 0.2549019608, 0.6274509803921569, + 0.7490196078, 0.2, 0.2509803922, 0.6313725490196078, 0.7529411765, 0.2039215686, 0.2431372549, + 0.6352941176470588, 0.7607843137, 0.2117647059, 0.2352941176, 0.6392156862745098, 0.768627451, + 0.2156862745, 0.2274509804, 0.6431372549019608, 0.7764705882, 0.2235294118, 0.2196078431, + 0.6470588235294118, 0.7843137255, 0.2274509804, 0.2117647059, 0.6509803921568628, + 0.7921568627, 0.2352941176, 0.2039215686, 0.6549019607843137, 0.8, 0.2392156863, 0.1960784314, + 0.6588235294117647, 0.8078431373, 0.2470588235, 0.1882352941, 0.6627450980392157, + 0.8156862745, 0.2509803922, 0.1803921569, 0.6666666666666666, 0.8235294118, 0.2549019608, + 0.1725490196, 0.6705882352941176, 0.831372549, 0.2588235294, 0.1647058824, 0.6745098039215687, + 0.8392156863, 0.2666666667, 0.1568627451, 0.6784313725490196, 0.8470588235, 0.2705882353, + 0.1490196078, 0.6823529411764706, 0.8549019608, 0.2784313725, 0.1411764706, + 0.6862745098039216, 0.862745098, 0.2823529412, 0.1333333333, 0.6901960784313725, 0.8705882353, + 0.2901960784, 0.1254901961, 0.6941176470588235, 0.8784313725, 0.2941176471, 0.1176470588, + 0.6980392156862745, 0.8862745098, 0.3019607843, 0.1098039216, 0.7019607843137254, + 0.8941176471, 0.3058823529, 0.1019607843, 0.7058823529411765, 0.9019607843, 0.3137254902, + 0.0941176471, 0.7098039215686275, 0.9098039216, 0.3176470588, 0.0862745098, + 0.7137254901960784, 0.9176470588, 0.3254901961, 0.0784313725, 0.7176470588235294, + 0.9254901961, 0.3294117647, 0.0705882353, 0.7215686274509804, 0.9333333333, 0.337254902, + 0.062745098, 0.7254901960784313, 0.9411764706, 0.3411764706, 0.0549019608, 0.7294117647058823, + 0.9490196078, 0.3490196078, 0.0470588235, 0.7333333333333333, 0.9568627451, 0.3529411765, + 0.0392156863, 0.7372549019607844, 0.9647058824, 0.3607843137, 0.031372549, 0.7411764705882353, + 0.9725490196, 0.3647058824, 0.0235294118, 0.7450980392156863, 0.9803921569, 0.3725490196, + 0.0156862745, 0.7490196078431373, 0.9882352941, 0.3725490196, 0.0039215686, + 0.7529411764705882, 0.9960784314, 0.3843137255, 0.0156862745, 0.7568627450980392, + 0.9960784314, 0.3921568627, 0.031372549, 0.7607843137254902, 0.9960784314, 0.4039215686, + 0.0470588235, 0.7647058823529411, 0.9960784314, 0.4117647059, 0.062745098, 0.7686274509803922, + 0.9960784314, 0.4235294118, 0.0784313725, 0.7725490196078432, 0.9960784314, 0.431372549, + 0.0941176471, 0.7764705882352941, 0.9960784314, 0.4431372549, 0.1098039216, + 0.7803921568627451, 0.9960784314, 0.4509803922, 0.1254901961, 0.7843137254901961, + 0.9960784314, 0.462745098, 0.1411764706, 0.788235294117647, 0.9960784314, 0.4705882353, + 0.1568627451, 0.792156862745098, 0.9960784314, 0.4823529412, 0.1725490196, 0.796078431372549, + 0.9960784314, 0.4901960784, 0.1882352941, 0.8, 0.9960784314, 0.5019607843, 0.2039215686, + 0.803921568627451, 0.9960784314, 0.5098039216, 0.2196078431, 0.807843137254902, 0.9960784314, + 0.5215686275, 0.2352941176, 0.8117647058823529, 0.9960784314, 0.5294117647, 0.2509803922, + 0.8156862745098039, 0.9960784314, 0.5411764706, 0.262745098, 0.8196078431372549, 0.9960784314, + 0.5490196078, 0.2784313725, 0.8235294117647058, 0.9960784314, 0.5607843137, 0.2941176471, + 0.8274509803921568, 0.9960784314, 0.568627451, 0.3098039216, 0.8313725490196079, 0.9960784314, + 0.5803921569, 0.3254901961, 0.8352941176470589, 0.9960784314, 0.5882352941, 0.3411764706, + 0.8392156862745098, 0.9960784314, 0.6, 0.3568627451, 0.8431372549019608, 0.9960784314, + 0.6078431373, 0.3725490196, 0.8470588235294118, 0.9960784314, 0.6196078431, 0.3882352941, + 0.8509803921568627, 0.9960784314, 0.6274509804, 0.4039215686, 0.8549019607843137, + 0.9960784314, 0.6392156863, 0.4196078431, 0.8588235294117647, 0.9960784314, 0.6470588235, + 0.4352941176, 0.8627450980392157, 0.9960784314, 0.6588235294, 0.4509803922, + 0.8666666666666667, 0.9960784314, 0.6666666667, 0.4666666667, 0.8705882352941177, + 0.9960784314, 0.6784313725, 0.4823529412, 0.8745098039215686, 0.9960784314, 0.6862745098, + 0.4980392157, 0.8784313725490196, 0.9960784314, 0.6980392157, 0.5137254902, + 0.8823529411764706, 0.9960784314, 0.7058823529, 0.5294117647, 0.8862745098039215, + 0.9960784314, 0.7176470588, 0.5450980392, 0.8901960784313725, 0.9960784314, 0.7254901961, + 0.5607843137, 0.8941176470588236, 0.9960784314, 0.737254902, 0.5764705882, 0.8980392156862745, + 0.9960784314, 0.7450980392, 0.5921568627, 0.9019607843137255, 0.9960784314, 0.7529411765, + 0.6078431373, 0.9058823529411765, 0.9960784314, 0.7607843137, 0.6235294118, + 0.9098039215686274, 0.9960784314, 0.7725490196, 0.6392156863, 0.9137254901960784, + 0.9960784314, 0.7803921569, 0.6549019608, 0.9176470588235294, 0.9960784314, 0.7921568627, + 0.6705882353, 0.9215686274509803, 0.9960784314, 0.8, 0.6862745098, 0.9254901960784314, + 0.9960784314, 0.8117647059, 0.7019607843, 0.9294117647058824, 0.9960784314, 0.8196078431, + 0.7176470588, 0.9333333333333333, 0.9960784314, 0.831372549, 0.7333333333, 0.9372549019607843, + 0.9960784314, 0.8392156863, 0.7490196078, 0.9411764705882354, 0.9960784314, 0.8509803922, + 0.7607843137, 0.9450980392156864, 0.9960784314, 0.8588235294, 0.7764705882, + 0.9490196078431372, 0.9960784314, 0.8705882353, 0.7921568627, 0.9529411764705882, + 0.9960784314, 0.8784313725, 0.8078431373, 0.9568627450980394, 0.9960784314, 0.8901960784, + 0.8235294118, 0.9607843137254903, 0.9960784314, 0.8980392157, 0.8392156863, + 0.9647058823529413, 0.9960784314, 0.9098039216, 0.8549019608, 0.9686274509803922, + 0.9960784314, 0.9176470588, 0.8705882353, 0.9725490196078431, 0.9960784314, 0.9294117647, + 0.8862745098, 0.9764705882352941, 0.9960784314, 0.937254902, 0.9019607843, 0.9803921568627451, + 0.9960784314, 0.9490196078, 0.9176470588, 0.984313725490196, 0.9960784314, 0.9568627451, + 0.9333333333, 0.9882352941176471, 0.9960784314, 0.968627451, 0.9490196078, 0.9921568627450981, + 0.9960784314, 0.9764705882, 0.9647058824, 0.996078431372549, 0.9960784314, 0.9882352941, + 0.9803921569, 1.0, 0.9960784314, 0.9882352941, 0.9803921569, ], }, { ColorSpace: 'RGB', Name: 'ge', RGBPoints: [ - 0.0, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.00392156862745098, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.00784313725490196, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.011764705882352941, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.01568627450980392, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.0196078431372549, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.023529411764705882, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.027450980392156862, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.03137254901960784, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.03529411764705882, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.0392156862745098, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.043137254901960784, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.047058823529411764, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.050980392156862744, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.054901960784313725, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.05882352941176471, - 0.0117647059, - 0.0078431373, - 0.0078431373, - 0.06274509803921569, - 0.0078431373, - 0.0156862745, - 0.0156862745, - 0.06666666666666667, - 0.0078431373, - 0.0235294118, - 0.0235294118, - 0.07058823529411765, - 0.0078431373, - 0.031372549, - 0.031372549, - 0.07450980392156863, - 0.0078431373, - 0.0392156863, - 0.0392156863, - 0.0784313725490196, - 0.0078431373, - 0.0470588235, - 0.0470588235, - 0.08235294117647059, - 0.0078431373, - 0.0549019608, - 0.0549019608, - 0.08627450980392157, - 0.0078431373, - 0.062745098, - 0.062745098, - 0.09019607843137255, - 0.0078431373, - 0.0705882353, - 0.0705882353, - 0.09411764705882353, - 0.0078431373, - 0.0784313725, - 0.0784313725, - 0.09803921568627451, - 0.0078431373, - 0.0901960784, - 0.0862745098, - 0.10196078431372549, - 0.0078431373, - 0.0980392157, - 0.0941176471, - 0.10588235294117647, - 0.0078431373, - 0.1058823529, - 0.1019607843, - 0.10980392156862745, - 0.0078431373, - 0.1137254902, - 0.1098039216, - 0.11372549019607843, - 0.0078431373, - 0.1215686275, - 0.1176470588, - 0.11764705882352942, - 0.0078431373, - 0.1294117647, - 0.1254901961, - 0.12156862745098039, - 0.0078431373, - 0.137254902, - 0.1333333333, - 0.12549019607843137, - 0.0078431373, - 0.1450980392, - 0.1411764706, - 0.12941176470588237, - 0.0078431373, - 0.1529411765, - 0.1490196078, - 0.13333333333333333, - 0.0078431373, - 0.1647058824, - 0.1568627451, - 0.13725490196078433, - 0.0078431373, - 0.1725490196, - 0.1647058824, - 0.1411764705882353, - 0.0078431373, - 0.1803921569, - 0.1725490196, - 0.1450980392156863, - 0.0078431373, - 0.1882352941, - 0.1803921569, - 0.14901960784313725, - 0.0078431373, - 0.1960784314, - 0.1882352941, - 0.15294117647058825, - 0.0078431373, - 0.2039215686, - 0.1960784314, - 0.1568627450980392, - 0.0078431373, - 0.2117647059, - 0.2039215686, - 0.1607843137254902, - 0.0078431373, - 0.2196078431, - 0.2117647059, - 0.16470588235294117, - 0.0078431373, - 0.2274509804, - 0.2196078431, - 0.16862745098039217, - 0.0078431373, - 0.2352941176, - 0.2274509804, - 0.17254901960784313, - 0.0078431373, - 0.2470588235, - 0.2352941176, - 0.17647058823529413, - 0.0078431373, - 0.2509803922, - 0.2431372549, - 0.1803921568627451, - 0.0078431373, - 0.2549019608, - 0.2509803922, - 0.1843137254901961, - 0.0078431373, - 0.262745098, - 0.2509803922, - 0.18823529411764706, - 0.0078431373, - 0.2705882353, - 0.2588235294, - 0.19215686274509805, - 0.0078431373, - 0.2784313725, - 0.2666666667, - 0.19607843137254902, - 0.0078431373, - 0.2862745098, - 0.2745098039, - 0.2, - 0.0078431373, - 0.2941176471, - 0.2823529412, - 0.20392156862745098, - 0.0078431373, - 0.3019607843, - 0.2901960784, - 0.20784313725490197, - 0.0078431373, - 0.3137254902, - 0.2980392157, - 0.21176470588235294, - 0.0078431373, - 0.3215686275, - 0.3058823529, - 0.21568627450980393, - 0.0078431373, - 0.3294117647, - 0.3137254902, - 0.2196078431372549, - 0.0078431373, - 0.337254902, - 0.3215686275, - 0.2235294117647059, - 0.0078431373, - 0.3450980392, - 0.3294117647, - 0.22745098039215686, - 0.0078431373, - 0.3529411765, - 0.337254902, - 0.23137254901960785, - 0.0078431373, - 0.3607843137, - 0.3450980392, - 0.23529411764705885, - 0.0078431373, - 0.368627451, - 0.3529411765, - 0.23921568627450984, - 0.0078431373, - 0.3764705882, - 0.3607843137, - 0.24313725490196078, - 0.0078431373, - 0.3843137255, - 0.368627451, - 0.24705882352941178, - 0.0078431373, - 0.3960784314, - 0.3764705882, - 0.25098039215686274, - 0.0078431373, - 0.4039215686, - 0.3843137255, - 0.2549019607843137, - 0.0078431373, - 0.4117647059, - 0.3921568627, - 0.25882352941176473, - 0.0078431373, - 0.4196078431, - 0.4, - 0.2627450980392157, - 0.0078431373, - 0.4274509804, - 0.4078431373, - 0.26666666666666666, - 0.0078431373, - 0.4352941176, - 0.4156862745, - 0.27058823529411763, - 0.0078431373, - 0.4431372549, - 0.4235294118, - 0.27450980392156865, - 0.0078431373, - 0.4509803922, - 0.431372549, - 0.2784313725490196, - 0.0078431373, - 0.4588235294, - 0.4392156863, - 0.2823529411764706, - 0.0078431373, - 0.4705882353, - 0.4470588235, - 0.28627450980392155, - 0.0078431373, - 0.4784313725, - 0.4549019608, - 0.2901960784313726, - 0.0078431373, - 0.4862745098, - 0.462745098, - 0.29411764705882354, - 0.0078431373, - 0.4941176471, - 0.4705882353, - 0.2980392156862745, - 0.0078431373, - 0.5019607843, - 0.4784313725, - 0.30196078431372547, - 0.0117647059, - 0.5098039216, - 0.4862745098, - 0.3058823529411765, - 0.0196078431, - 0.5019607843, - 0.4941176471, - 0.30980392156862746, - 0.0274509804, - 0.4941176471, - 0.5058823529, - 0.3137254901960784, - 0.0352941176, - 0.4862745098, - 0.5137254902, - 0.3176470588235294, - 0.0431372549, - 0.4784313725, - 0.5215686275, - 0.3215686274509804, - 0.0509803922, - 0.4705882353, - 0.5294117647, - 0.3254901960784314, - 0.0588235294, - 0.462745098, - 0.537254902, - 0.32941176470588235, - 0.0666666667, - 0.4549019608, - 0.5450980392, - 0.3333333333333333, - 0.0745098039, - 0.4470588235, - 0.5529411765, - 0.33725490196078434, - 0.0823529412, - 0.4392156863, - 0.5607843137, - 0.3411764705882353, - 0.0901960784, - 0.431372549, - 0.568627451, - 0.34509803921568627, - 0.0980392157, - 0.4235294118, - 0.5764705882, - 0.34901960784313724, - 0.1058823529, - 0.4156862745, - 0.5843137255, - 0.35294117647058826, - 0.1137254902, - 0.4078431373, - 0.5921568627, - 0.3568627450980392, - 0.1215686275, - 0.4, - 0.6, - 0.3607843137254902, - 0.1294117647, - 0.3921568627, - 0.6078431373, - 0.36470588235294116, - 0.137254902, - 0.3843137255, - 0.6156862745, - 0.3686274509803922, - 0.1450980392, - 0.3764705882, - 0.6235294118, - 0.37254901960784315, - 0.1529411765, - 0.368627451, - 0.631372549, - 0.3764705882352941, - 0.1607843137, - 0.3607843137, - 0.6392156863, - 0.3803921568627451, - 0.168627451, - 0.3529411765, - 0.6470588235, - 0.3843137254901961, - 0.1764705882, - 0.3450980392, - 0.6549019608, - 0.38823529411764707, - 0.1843137255, - 0.337254902, - 0.662745098, - 0.39215686274509803, - 0.1921568627, - 0.3294117647, - 0.6705882353, - 0.396078431372549, - 0.2, - 0.3215686275, - 0.6784313725, - 0.4, - 0.2078431373, - 0.3137254902, - 0.6862745098, - 0.403921568627451, - 0.2156862745, - 0.3058823529, - 0.6941176471, - 0.40784313725490196, - 0.2235294118, - 0.2980392157, - 0.7019607843, - 0.4117647058823529, - 0.231372549, - 0.2901960784, - 0.7098039216, - 0.41568627450980394, - 0.2392156863, - 0.2823529412, - 0.7176470588, - 0.4196078431372549, - 0.2470588235, - 0.2745098039, - 0.7254901961, - 0.4235294117647059, - 0.2509803922, - 0.2666666667, - 0.7333333333, - 0.42745098039215684, - 0.2509803922, - 0.2588235294, - 0.7411764706, - 0.43137254901960786, - 0.2588235294, - 0.2509803922, - 0.7490196078, - 0.43529411764705883, - 0.2666666667, - 0.2509803922, - 0.7490196078, - 0.4392156862745098, - 0.2745098039, - 0.2431372549, - 0.7568627451, - 0.44313725490196076, - 0.2823529412, - 0.2352941176, - 0.7647058824, - 0.4470588235294118, - 0.2901960784, - 0.2274509804, - 0.7725490196, - 0.45098039215686275, - 0.2980392157, - 0.2196078431, - 0.7803921569, - 0.4549019607843137, - 0.3058823529, - 0.2117647059, - 0.7882352941, - 0.4588235294117647, - 0.3137254902, - 0.2039215686, - 0.7960784314, - 0.4627450980392157, - 0.3215686275, - 0.1960784314, - 0.8039215686, - 0.4666666666666667, - 0.3294117647, - 0.1882352941, - 0.8117647059, - 0.4705882352941177, - 0.337254902, - 0.1803921569, - 0.8196078431, - 0.4745098039215686, - 0.3450980392, - 0.1725490196, - 0.8274509804, - 0.4784313725490197, - 0.3529411765, - 0.1647058824, - 0.8352941176, - 0.48235294117647065, - 0.3607843137, - 0.1568627451, - 0.8431372549, - 0.48627450980392156, - 0.368627451, - 0.1490196078, - 0.8509803922, - 0.49019607843137253, - 0.3764705882, - 0.1411764706, - 0.8588235294, - 0.49411764705882355, - 0.3843137255, - 0.1333333333, - 0.8666666667, - 0.4980392156862745, - 0.3921568627, - 0.1254901961, - 0.8745098039, - 0.5019607843137255, - 0.4, - 0.1176470588, - 0.8823529412, - 0.5058823529411764, - 0.4078431373, - 0.1098039216, - 0.8901960784, - 0.5098039215686274, - 0.4156862745, - 0.1019607843, - 0.8980392157, - 0.5137254901960784, - 0.4235294118, - 0.0941176471, - 0.9058823529, - 0.5176470588235295, - 0.431372549, - 0.0862745098, - 0.9137254902, - 0.5215686274509804, - 0.4392156863, - 0.0784313725, - 0.9215686275, - 0.5254901960784314, - 0.4470588235, - 0.0705882353, - 0.9294117647, - 0.5294117647058824, - 0.4549019608, - 0.062745098, - 0.937254902, - 0.5333333333333333, - 0.462745098, - 0.0549019608, - 0.9450980392, - 0.5372549019607843, - 0.4705882353, - 0.0470588235, - 0.9529411765, - 0.5411764705882353, - 0.4784313725, - 0.0392156863, - 0.9607843137, - 0.5450980392156862, - 0.4862745098, - 0.031372549, - 0.968627451, - 0.5490196078431373, - 0.4941176471, - 0.0235294118, - 0.9764705882, - 0.5529411764705883, - 0.4980392157, - 0.0156862745, - 0.9843137255, - 0.5568627450980392, - 0.5058823529, - 0.0078431373, - 0.9921568627, - 0.5607843137254902, - 0.5137254902, - 0.0156862745, - 0.9803921569, - 0.5647058823529412, - 0.5215686275, - 0.0235294118, - 0.9647058824, - 0.5686274509803921, - 0.5294117647, - 0.0352941176, - 0.9490196078, - 0.5725490196078431, - 0.537254902, - 0.0431372549, - 0.9333333333, - 0.5764705882352941, - 0.5450980392, - 0.0509803922, - 0.9176470588, - 0.5803921568627451, - 0.5529411765, - 0.062745098, - 0.9019607843, - 0.5843137254901961, - 0.5607843137, - 0.0705882353, - 0.8862745098, - 0.5882352941176471, - 0.568627451, - 0.0784313725, - 0.8705882353, - 0.592156862745098, - 0.5764705882, - 0.0901960784, - 0.8549019608, - 0.596078431372549, - 0.5843137255, - 0.0980392157, - 0.8392156863, - 0.6, - 0.5921568627, - 0.1098039216, - 0.8235294118, - 0.6039215686274509, - 0.6, - 0.1176470588, - 0.8078431373, - 0.6078431372549019, - 0.6078431373, - 0.1254901961, - 0.7921568627, - 0.611764705882353, - 0.6156862745, - 0.137254902, - 0.7764705882, - 0.615686274509804, - 0.6235294118, - 0.1450980392, - 0.7607843137, - 0.6196078431372549, - 0.631372549, - 0.1529411765, - 0.7490196078, - 0.6235294117647059, - 0.6392156863, - 0.1647058824, - 0.737254902, - 0.6274509803921569, - 0.6470588235, - 0.1725490196, - 0.7215686275, - 0.6313725490196078, - 0.6549019608, - 0.1843137255, - 0.7058823529, - 0.6352941176470588, - 0.662745098, - 0.1921568627, - 0.6901960784, - 0.6392156862745098, - 0.6705882353, - 0.2, - 0.6745098039, - 0.6431372549019608, - 0.6784313725, - 0.2117647059, - 0.6588235294, - 0.6470588235294118, - 0.6862745098, - 0.2196078431, - 0.6431372549, - 0.6509803921568628, - 0.6941176471, - 0.2274509804, - 0.6274509804, - 0.6549019607843137, - 0.7019607843, - 0.2392156863, - 0.6117647059, - 0.6588235294117647, - 0.7098039216, - 0.2470588235, - 0.5960784314, - 0.6627450980392157, - 0.7176470588, - 0.2509803922, - 0.5803921569, - 0.6666666666666666, - 0.7254901961, - 0.2588235294, - 0.5647058824, - 0.6705882352941176, - 0.7333333333, - 0.2666666667, - 0.5490196078, - 0.6745098039215687, - 0.7411764706, - 0.2784313725, - 0.5333333333, - 0.6784313725490196, - 0.7490196078, - 0.2862745098, - 0.5176470588, - 0.6823529411764706, - 0.7490196078, - 0.2941176471, - 0.5019607843, - 0.6862745098039216, - 0.7529411765, - 0.3058823529, - 0.4862745098, - 0.6901960784313725, - 0.7607843137, - 0.3137254902, - 0.4705882353, - 0.6941176470588235, - 0.768627451, - 0.3215686275, - 0.4549019608, - 0.6980392156862745, - 0.7764705882, - 0.3333333333, - 0.4392156863, - 0.7019607843137254, - 0.7843137255, - 0.3411764706, - 0.4235294118, - 0.7058823529411765, - 0.7921568627, - 0.3529411765, - 0.4078431373, - 0.7098039215686275, - 0.8, - 0.3607843137, - 0.3921568627, - 0.7137254901960784, - 0.8078431373, - 0.368627451, - 0.3764705882, - 0.7176470588235294, - 0.8156862745, - 0.3803921569, - 0.3607843137, - 0.7215686274509804, - 0.8235294118, - 0.3882352941, - 0.3450980392, - 0.7254901960784313, - 0.831372549, - 0.3960784314, - 0.3294117647, - 0.7294117647058823, - 0.8392156863, - 0.4078431373, - 0.3137254902, - 0.7333333333333333, - 0.8470588235, - 0.4156862745, - 0.2980392157, - 0.7372549019607844, - 0.8549019608, - 0.4274509804, - 0.2823529412, - 0.7411764705882353, - 0.862745098, - 0.4352941176, - 0.2666666667, - 0.7450980392156863, - 0.8705882353, - 0.4431372549, - 0.2509803922, - 0.7490196078431373, - 0.8784313725, - 0.4549019608, - 0.2431372549, - 0.7529411764705882, - 0.8862745098, - 0.462745098, - 0.2274509804, - 0.7568627450980392, - 0.8941176471, - 0.4705882353, - 0.2117647059, - 0.7607843137254902, - 0.9019607843, - 0.4823529412, - 0.1960784314, - 0.7647058823529411, - 0.9098039216, - 0.4901960784, - 0.1803921569, - 0.7686274509803922, - 0.9176470588, - 0.4980392157, - 0.1647058824, - 0.7725490196078432, - 0.9254901961, - 0.5098039216, - 0.1490196078, - 0.7764705882352941, - 0.9333333333, - 0.5176470588, - 0.1333333333, - 0.7803921568627451, - 0.9411764706, - 0.5294117647, - 0.1176470588, - 0.7843137254901961, - 0.9490196078, - 0.537254902, - 0.1019607843, - 0.788235294117647, - 0.9568627451, - 0.5450980392, - 0.0862745098, - 0.792156862745098, - 0.9647058824, - 0.5568627451, - 0.0705882353, - 0.796078431372549, - 0.9725490196, - 0.5647058824, - 0.0549019608, - 0.8, - 0.9803921569, - 0.5725490196, - 0.0392156863, - 0.803921568627451, - 0.9882352941, - 0.5843137255, - 0.0235294118, - 0.807843137254902, - 0.9921568627, - 0.5921568627, - 0.0078431373, - 0.8117647058823529, - 0.9921568627, - 0.6039215686, - 0.0274509804, - 0.8156862745098039, - 0.9921568627, - 0.6117647059, - 0.0509803922, - 0.8196078431372549, - 0.9921568627, - 0.6196078431, - 0.0745098039, - 0.8235294117647058, - 0.9921568627, - 0.631372549, - 0.0980392157, - 0.8274509803921568, - 0.9921568627, - 0.6392156863, - 0.1215686275, - 0.8313725490196079, - 0.9921568627, - 0.6470588235, - 0.1411764706, - 0.8352941176470589, - 0.9921568627, - 0.6588235294, - 0.1647058824, - 0.8392156862745098, - 0.9921568627, - 0.6666666667, - 0.1882352941, - 0.8431372549019608, - 0.9921568627, - 0.6784313725, - 0.2117647059, - 0.8470588235294118, - 0.9921568627, - 0.6862745098, - 0.2352941176, - 0.8509803921568627, - 0.9921568627, - 0.6941176471, - 0.2509803922, - 0.8549019607843137, - 0.9921568627, - 0.7058823529, - 0.2705882353, - 0.8588235294117647, - 0.9921568627, - 0.7137254902, - 0.2941176471, - 0.8627450980392157, - 0.9921568627, - 0.7215686275, - 0.3176470588, - 0.8666666666666667, - 0.9921568627, - 0.7333333333, - 0.3411764706, - 0.8705882352941177, - 0.9921568627, - 0.7411764706, - 0.3647058824, - 0.8745098039215686, - 0.9921568627, - 0.7490196078, - 0.3843137255, - 0.8784313725490196, - 0.9921568627, - 0.7529411765, - 0.4078431373, - 0.8823529411764706, - 0.9921568627, - 0.7607843137, - 0.431372549, - 0.8862745098039215, - 0.9921568627, - 0.7725490196, - 0.4549019608, - 0.8901960784313725, - 0.9921568627, - 0.7803921569, - 0.4784313725, - 0.8941176470588236, - 0.9921568627, - 0.7882352941, - 0.4980392157, - 0.8980392156862745, - 0.9921568627, - 0.8, - 0.5215686275, - 0.9019607843137255, - 0.9921568627, - 0.8078431373, - 0.5450980392, - 0.9058823529411765, - 0.9921568627, - 0.8156862745, - 0.568627451, - 0.9098039215686274, - 0.9921568627, - 0.8274509804, - 0.5921568627, - 0.9137254901960784, - 0.9921568627, - 0.8352941176, - 0.6156862745, - 0.9176470588235294, - 0.9921568627, - 0.8470588235, - 0.6352941176, - 0.9215686274509803, - 0.9921568627, - 0.8549019608, - 0.6588235294, - 0.9254901960784314, - 0.9921568627, - 0.862745098, - 0.6823529412, - 0.9294117647058824, - 0.9921568627, - 0.8745098039, - 0.7058823529, - 0.9333333333333333, - 0.9921568627, - 0.8823529412, - 0.7294117647, - 0.9372549019607843, - 0.9921568627, - 0.8901960784, - 0.7490196078, - 0.9411764705882354, - 0.9921568627, - 0.9019607843, - 0.7647058824, - 0.9450980392156864, - 0.9921568627, - 0.9098039216, - 0.7882352941, - 0.9490196078431372, - 0.9921568627, - 0.9215686275, - 0.8117647059, - 0.9529411764705882, - 0.9921568627, - 0.9294117647, - 0.8352941176, - 0.9568627450980394, - 0.9921568627, - 0.937254902, - 0.8588235294, - 0.9607843137254903, - 0.9921568627, - 0.9490196078, - 0.8784313725, - 0.9647058823529413, - 0.9921568627, - 0.9568627451, - 0.9019607843, - 0.9686274509803922, - 0.9921568627, - 0.9647058824, - 0.9254901961, - 0.9725490196078431, - 0.9921568627, - 0.9764705882, - 0.9490196078, - 0.9764705882352941, - 0.9921568627, - 0.9843137255, - 0.9725490196, - 0.9803921568627451, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.984313725490196, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.9882352941176471, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.9921568627450981, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.996078431372549, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 1.0, - 0.9921568627, - 0.9921568627, - 0.9921568627, + 0.0, 0.0078431373, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0078431373, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0078431373, 0.0078431373, 0.0078431373, + 0.011764705882352941, 0.0078431373, 0.0078431373, 0.0078431373, 0.01568627450980392, + 0.0078431373, 0.0078431373, 0.0078431373, 0.0196078431372549, 0.0078431373, 0.0078431373, + 0.0078431373, 0.023529411764705882, 0.0078431373, 0.0078431373, 0.0078431373, + 0.027450980392156862, 0.0078431373, 0.0078431373, 0.0078431373, 0.03137254901960784, + 0.0078431373, 0.0078431373, 0.0078431373, 0.03529411764705882, 0.0078431373, 0.0078431373, + 0.0078431373, 0.0392156862745098, 0.0078431373, 0.0078431373, 0.0078431373, + 0.043137254901960784, 0.0078431373, 0.0078431373, 0.0078431373, 0.047058823529411764, + 0.0078431373, 0.0078431373, 0.0078431373, 0.050980392156862744, 0.0078431373, 0.0078431373, + 0.0078431373, 0.054901960784313725, 0.0078431373, 0.0078431373, 0.0078431373, + 0.05882352941176471, 0.0117647059, 0.0078431373, 0.0078431373, 0.06274509803921569, + 0.0078431373, 0.0156862745, 0.0156862745, 0.06666666666666667, 0.0078431373, 0.0235294118, + 0.0235294118, 0.07058823529411765, 0.0078431373, 0.031372549, 0.031372549, + 0.07450980392156863, 0.0078431373, 0.0392156863, 0.0392156863, 0.0784313725490196, + 0.0078431373, 0.0470588235, 0.0470588235, 0.08235294117647059, 0.0078431373, 0.0549019608, + 0.0549019608, 0.08627450980392157, 0.0078431373, 0.062745098, 0.062745098, + 0.09019607843137255, 0.0078431373, 0.0705882353, 0.0705882353, 0.09411764705882353, + 0.0078431373, 0.0784313725, 0.0784313725, 0.09803921568627451, 0.0078431373, 0.0901960784, + 0.0862745098, 0.10196078431372549, 0.0078431373, 0.0980392157, 0.0941176471, + 0.10588235294117647, 0.0078431373, 0.1058823529, 0.1019607843, 0.10980392156862745, + 0.0078431373, 0.1137254902, 0.1098039216, 0.11372549019607843, 0.0078431373, 0.1215686275, + 0.1176470588, 0.11764705882352942, 0.0078431373, 0.1294117647, 0.1254901961, + 0.12156862745098039, 0.0078431373, 0.137254902, 0.1333333333, 0.12549019607843137, + 0.0078431373, 0.1450980392, 0.1411764706, 0.12941176470588237, 0.0078431373, 0.1529411765, + 0.1490196078, 0.13333333333333333, 0.0078431373, 0.1647058824, 0.1568627451, + 0.13725490196078433, 0.0078431373, 0.1725490196, 0.1647058824, 0.1411764705882353, + 0.0078431373, 0.1803921569, 0.1725490196, 0.1450980392156863, 0.0078431373, 0.1882352941, + 0.1803921569, 0.14901960784313725, 0.0078431373, 0.1960784314, 0.1882352941, + 0.15294117647058825, 0.0078431373, 0.2039215686, 0.1960784314, 0.1568627450980392, + 0.0078431373, 0.2117647059, 0.2039215686, 0.1607843137254902, 0.0078431373, 0.2196078431, + 0.2117647059, 0.16470588235294117, 0.0078431373, 0.2274509804, 0.2196078431, + 0.16862745098039217, 0.0078431373, 0.2352941176, 0.2274509804, 0.17254901960784313, + 0.0078431373, 0.2470588235, 0.2352941176, 0.17647058823529413, 0.0078431373, 0.2509803922, + 0.2431372549, 0.1803921568627451, 0.0078431373, 0.2549019608, 0.2509803922, + 0.1843137254901961, 0.0078431373, 0.262745098, 0.2509803922, 0.18823529411764706, + 0.0078431373, 0.2705882353, 0.2588235294, 0.19215686274509805, 0.0078431373, 0.2784313725, + 0.2666666667, 0.19607843137254902, 0.0078431373, 0.2862745098, 0.2745098039, 0.2, + 0.0078431373, 0.2941176471, 0.2823529412, 0.20392156862745098, 0.0078431373, 0.3019607843, + 0.2901960784, 0.20784313725490197, 0.0078431373, 0.3137254902, 0.2980392157, + 0.21176470588235294, 0.0078431373, 0.3215686275, 0.3058823529, 0.21568627450980393, + 0.0078431373, 0.3294117647, 0.3137254902, 0.2196078431372549, 0.0078431373, 0.337254902, + 0.3215686275, 0.2235294117647059, 0.0078431373, 0.3450980392, 0.3294117647, + 0.22745098039215686, 0.0078431373, 0.3529411765, 0.337254902, 0.23137254901960785, + 0.0078431373, 0.3607843137, 0.3450980392, 0.23529411764705885, 0.0078431373, 0.368627451, + 0.3529411765, 0.23921568627450984, 0.0078431373, 0.3764705882, 0.3607843137, + 0.24313725490196078, 0.0078431373, 0.3843137255, 0.368627451, 0.24705882352941178, + 0.0078431373, 0.3960784314, 0.3764705882, 0.25098039215686274, 0.0078431373, 0.4039215686, + 0.3843137255, 0.2549019607843137, 0.0078431373, 0.4117647059, 0.3921568627, + 0.25882352941176473, 0.0078431373, 0.4196078431, 0.4, 0.2627450980392157, 0.0078431373, + 0.4274509804, 0.4078431373, 0.26666666666666666, 0.0078431373, 0.4352941176, 0.4156862745, + 0.27058823529411763, 0.0078431373, 0.4431372549, 0.4235294118, 0.27450980392156865, + 0.0078431373, 0.4509803922, 0.431372549, 0.2784313725490196, 0.0078431373, 0.4588235294, + 0.4392156863, 0.2823529411764706, 0.0078431373, 0.4705882353, 0.4470588235, + 0.28627450980392155, 0.0078431373, 0.4784313725, 0.4549019608, 0.2901960784313726, + 0.0078431373, 0.4862745098, 0.462745098, 0.29411764705882354, 0.0078431373, 0.4941176471, + 0.4705882353, 0.2980392156862745, 0.0078431373, 0.5019607843, 0.4784313725, + 0.30196078431372547, 0.0117647059, 0.5098039216, 0.4862745098, 0.3058823529411765, + 0.0196078431, 0.5019607843, 0.4941176471, 0.30980392156862746, 0.0274509804, 0.4941176471, + 0.5058823529, 0.3137254901960784, 0.0352941176, 0.4862745098, 0.5137254902, + 0.3176470588235294, 0.0431372549, 0.4784313725, 0.5215686275, 0.3215686274509804, + 0.0509803922, 0.4705882353, 0.5294117647, 0.3254901960784314, 0.0588235294, 0.462745098, + 0.537254902, 0.32941176470588235, 0.0666666667, 0.4549019608, 0.5450980392, + 0.3333333333333333, 0.0745098039, 0.4470588235, 0.5529411765, 0.33725490196078434, + 0.0823529412, 0.4392156863, 0.5607843137, 0.3411764705882353, 0.0901960784, 0.431372549, + 0.568627451, 0.34509803921568627, 0.0980392157, 0.4235294118, 0.5764705882, + 0.34901960784313724, 0.1058823529, 0.4156862745, 0.5843137255, 0.35294117647058826, + 0.1137254902, 0.4078431373, 0.5921568627, 0.3568627450980392, 0.1215686275, 0.4, 0.6, + 0.3607843137254902, 0.1294117647, 0.3921568627, 0.6078431373, 0.36470588235294116, + 0.137254902, 0.3843137255, 0.6156862745, 0.3686274509803922, 0.1450980392, 0.3764705882, + 0.6235294118, 0.37254901960784315, 0.1529411765, 0.368627451, 0.631372549, 0.3764705882352941, + 0.1607843137, 0.3607843137, 0.6392156863, 0.3803921568627451, 0.168627451, 0.3529411765, + 0.6470588235, 0.3843137254901961, 0.1764705882, 0.3450980392, 0.6549019608, + 0.38823529411764707, 0.1843137255, 0.337254902, 0.662745098, 0.39215686274509803, + 0.1921568627, 0.3294117647, 0.6705882353, 0.396078431372549, 0.2, 0.3215686275, 0.6784313725, + 0.4, 0.2078431373, 0.3137254902, 0.6862745098, 0.403921568627451, 0.2156862745, 0.3058823529, + 0.6941176471, 0.40784313725490196, 0.2235294118, 0.2980392157, 0.7019607843, + 0.4117647058823529, 0.231372549, 0.2901960784, 0.7098039216, 0.41568627450980394, + 0.2392156863, 0.2823529412, 0.7176470588, 0.4196078431372549, 0.2470588235, 0.2745098039, + 0.7254901961, 0.4235294117647059, 0.2509803922, 0.2666666667, 0.7333333333, + 0.42745098039215684, 0.2509803922, 0.2588235294, 0.7411764706, 0.43137254901960786, + 0.2588235294, 0.2509803922, 0.7490196078, 0.43529411764705883, 0.2666666667, 0.2509803922, + 0.7490196078, 0.4392156862745098, 0.2745098039, 0.2431372549, 0.7568627451, + 0.44313725490196076, 0.2823529412, 0.2352941176, 0.7647058824, 0.4470588235294118, + 0.2901960784, 0.2274509804, 0.7725490196, 0.45098039215686275, 0.2980392157, 0.2196078431, + 0.7803921569, 0.4549019607843137, 0.3058823529, 0.2117647059, 0.7882352941, + 0.4588235294117647, 0.3137254902, 0.2039215686, 0.7960784314, 0.4627450980392157, + 0.3215686275, 0.1960784314, 0.8039215686, 0.4666666666666667, 0.3294117647, 0.1882352941, + 0.8117647059, 0.4705882352941177, 0.337254902, 0.1803921569, 0.8196078431, 0.4745098039215686, + 0.3450980392, 0.1725490196, 0.8274509804, 0.4784313725490197, 0.3529411765, 0.1647058824, + 0.8352941176, 0.48235294117647065, 0.3607843137, 0.1568627451, 0.8431372549, + 0.48627450980392156, 0.368627451, 0.1490196078, 0.8509803922, 0.49019607843137253, + 0.3764705882, 0.1411764706, 0.8588235294, 0.49411764705882355, 0.3843137255, 0.1333333333, + 0.8666666667, 0.4980392156862745, 0.3921568627, 0.1254901961, 0.8745098039, + 0.5019607843137255, 0.4, 0.1176470588, 0.8823529412, 0.5058823529411764, 0.4078431373, + 0.1098039216, 0.8901960784, 0.5098039215686274, 0.4156862745, 0.1019607843, 0.8980392157, + 0.5137254901960784, 0.4235294118, 0.0941176471, 0.9058823529, 0.5176470588235295, 0.431372549, + 0.0862745098, 0.9137254902, 0.5215686274509804, 0.4392156863, 0.0784313725, 0.9215686275, + 0.5254901960784314, 0.4470588235, 0.0705882353, 0.9294117647, 0.5294117647058824, + 0.4549019608, 0.062745098, 0.937254902, 0.5333333333333333, 0.462745098, 0.0549019608, + 0.9450980392, 0.5372549019607843, 0.4705882353, 0.0470588235, 0.9529411765, + 0.5411764705882353, 0.4784313725, 0.0392156863, 0.9607843137, 0.5450980392156862, + 0.4862745098, 0.031372549, 0.968627451, 0.5490196078431373, 0.4941176471, 0.0235294118, + 0.9764705882, 0.5529411764705883, 0.4980392157, 0.0156862745, 0.9843137255, + 0.5568627450980392, 0.5058823529, 0.0078431373, 0.9921568627, 0.5607843137254902, + 0.5137254902, 0.0156862745, 0.9803921569, 0.5647058823529412, 0.5215686275, 0.0235294118, + 0.9647058824, 0.5686274509803921, 0.5294117647, 0.0352941176, 0.9490196078, + 0.5725490196078431, 0.537254902, 0.0431372549, 0.9333333333, 0.5764705882352941, 0.5450980392, + 0.0509803922, 0.9176470588, 0.5803921568627451, 0.5529411765, 0.062745098, 0.9019607843, + 0.5843137254901961, 0.5607843137, 0.0705882353, 0.8862745098, 0.5882352941176471, 0.568627451, + 0.0784313725, 0.8705882353, 0.592156862745098, 0.5764705882, 0.0901960784, 0.8549019608, + 0.596078431372549, 0.5843137255, 0.0980392157, 0.8392156863, 0.6, 0.5921568627, 0.1098039216, + 0.8235294118, 0.6039215686274509, 0.6, 0.1176470588, 0.8078431373, 0.6078431372549019, + 0.6078431373, 0.1254901961, 0.7921568627, 0.611764705882353, 0.6156862745, 0.137254902, + 0.7764705882, 0.615686274509804, 0.6235294118, 0.1450980392, 0.7607843137, 0.6196078431372549, + 0.631372549, 0.1529411765, 0.7490196078, 0.6235294117647059, 0.6392156863, 0.1647058824, + 0.737254902, 0.6274509803921569, 0.6470588235, 0.1725490196, 0.7215686275, 0.6313725490196078, + 0.6549019608, 0.1843137255, 0.7058823529, 0.6352941176470588, 0.662745098, 0.1921568627, + 0.6901960784, 0.6392156862745098, 0.6705882353, 0.2, 0.6745098039, 0.6431372549019608, + 0.6784313725, 0.2117647059, 0.6588235294, 0.6470588235294118, 0.6862745098, 0.2196078431, + 0.6431372549, 0.6509803921568628, 0.6941176471, 0.2274509804, 0.6274509804, + 0.6549019607843137, 0.7019607843, 0.2392156863, 0.6117647059, 0.6588235294117647, + 0.7098039216, 0.2470588235, 0.5960784314, 0.6627450980392157, 0.7176470588, 0.2509803922, + 0.5803921569, 0.6666666666666666, 0.7254901961, 0.2588235294, 0.5647058824, + 0.6705882352941176, 0.7333333333, 0.2666666667, 0.5490196078, 0.6745098039215687, + 0.7411764706, 0.2784313725, 0.5333333333, 0.6784313725490196, 0.7490196078, 0.2862745098, + 0.5176470588, 0.6823529411764706, 0.7490196078, 0.2941176471, 0.5019607843, + 0.6862745098039216, 0.7529411765, 0.3058823529, 0.4862745098, 0.6901960784313725, + 0.7607843137, 0.3137254902, 0.4705882353, 0.6941176470588235, 0.768627451, 0.3215686275, + 0.4549019608, 0.6980392156862745, 0.7764705882, 0.3333333333, 0.4392156863, + 0.7019607843137254, 0.7843137255, 0.3411764706, 0.4235294118, 0.7058823529411765, + 0.7921568627, 0.3529411765, 0.4078431373, 0.7098039215686275, 0.8, 0.3607843137, 0.3921568627, + 0.7137254901960784, 0.8078431373, 0.368627451, 0.3764705882, 0.7176470588235294, 0.8156862745, + 0.3803921569, 0.3607843137, 0.7215686274509804, 0.8235294118, 0.3882352941, 0.3450980392, + 0.7254901960784313, 0.831372549, 0.3960784314, 0.3294117647, 0.7294117647058823, 0.8392156863, + 0.4078431373, 0.3137254902, 0.7333333333333333, 0.8470588235, 0.4156862745, 0.2980392157, + 0.7372549019607844, 0.8549019608, 0.4274509804, 0.2823529412, 0.7411764705882353, 0.862745098, + 0.4352941176, 0.2666666667, 0.7450980392156863, 0.8705882353, 0.4431372549, 0.2509803922, + 0.7490196078431373, 0.8784313725, 0.4549019608, 0.2431372549, 0.7529411764705882, + 0.8862745098, 0.462745098, 0.2274509804, 0.7568627450980392, 0.8941176471, 0.4705882353, + 0.2117647059, 0.7607843137254902, 0.9019607843, 0.4823529412, 0.1960784314, + 0.7647058823529411, 0.9098039216, 0.4901960784, 0.1803921569, 0.7686274509803922, + 0.9176470588, 0.4980392157, 0.1647058824, 0.7725490196078432, 0.9254901961, 0.5098039216, + 0.1490196078, 0.7764705882352941, 0.9333333333, 0.5176470588, 0.1333333333, + 0.7803921568627451, 0.9411764706, 0.5294117647, 0.1176470588, 0.7843137254901961, + 0.9490196078, 0.537254902, 0.1019607843, 0.788235294117647, 0.9568627451, 0.5450980392, + 0.0862745098, 0.792156862745098, 0.9647058824, 0.5568627451, 0.0705882353, 0.796078431372549, + 0.9725490196, 0.5647058824, 0.0549019608, 0.8, 0.9803921569, 0.5725490196, 0.0392156863, + 0.803921568627451, 0.9882352941, 0.5843137255, 0.0235294118, 0.807843137254902, 0.9921568627, + 0.5921568627, 0.0078431373, 0.8117647058823529, 0.9921568627, 0.6039215686, 0.0274509804, + 0.8156862745098039, 0.9921568627, 0.6117647059, 0.0509803922, 0.8196078431372549, + 0.9921568627, 0.6196078431, 0.0745098039, 0.8235294117647058, 0.9921568627, 0.631372549, + 0.0980392157, 0.8274509803921568, 0.9921568627, 0.6392156863, 0.1215686275, + 0.8313725490196079, 0.9921568627, 0.6470588235, 0.1411764706, 0.8352941176470589, + 0.9921568627, 0.6588235294, 0.1647058824, 0.8392156862745098, 0.9921568627, 0.6666666667, + 0.1882352941, 0.8431372549019608, 0.9921568627, 0.6784313725, 0.2117647059, + 0.8470588235294118, 0.9921568627, 0.6862745098, 0.2352941176, 0.8509803921568627, + 0.9921568627, 0.6941176471, 0.2509803922, 0.8549019607843137, 0.9921568627, 0.7058823529, + 0.2705882353, 0.8588235294117647, 0.9921568627, 0.7137254902, 0.2941176471, + 0.8627450980392157, 0.9921568627, 0.7215686275, 0.3176470588, 0.8666666666666667, + 0.9921568627, 0.7333333333, 0.3411764706, 0.8705882352941177, 0.9921568627, 0.7411764706, + 0.3647058824, 0.8745098039215686, 0.9921568627, 0.7490196078, 0.3843137255, + 0.8784313725490196, 0.9921568627, 0.7529411765, 0.4078431373, 0.8823529411764706, + 0.9921568627, 0.7607843137, 0.431372549, 0.8862745098039215, 0.9921568627, 0.7725490196, + 0.4549019608, 0.8901960784313725, 0.9921568627, 0.7803921569, 0.4784313725, + 0.8941176470588236, 0.9921568627, 0.7882352941, 0.4980392157, 0.8980392156862745, + 0.9921568627, 0.8, 0.5215686275, 0.9019607843137255, 0.9921568627, 0.8078431373, 0.5450980392, + 0.9058823529411765, 0.9921568627, 0.8156862745, 0.568627451, 0.9098039215686274, 0.9921568627, + 0.8274509804, 0.5921568627, 0.9137254901960784, 0.9921568627, 0.8352941176, 0.6156862745, + 0.9176470588235294, 0.9921568627, 0.8470588235, 0.6352941176, 0.9215686274509803, + 0.9921568627, 0.8549019608, 0.6588235294, 0.9254901960784314, 0.9921568627, 0.862745098, + 0.6823529412, 0.9294117647058824, 0.9921568627, 0.8745098039, 0.7058823529, + 0.9333333333333333, 0.9921568627, 0.8823529412, 0.7294117647, 0.9372549019607843, + 0.9921568627, 0.8901960784, 0.7490196078, 0.9411764705882354, 0.9921568627, 0.9019607843, + 0.7647058824, 0.9450980392156864, 0.9921568627, 0.9098039216, 0.7882352941, + 0.9490196078431372, 0.9921568627, 0.9215686275, 0.8117647059, 0.9529411764705882, + 0.9921568627, 0.9294117647, 0.8352941176, 0.9568627450980394, 0.9921568627, 0.937254902, + 0.8588235294, 0.9607843137254903, 0.9921568627, 0.9490196078, 0.8784313725, + 0.9647058823529413, 0.9921568627, 0.9568627451, 0.9019607843, 0.9686274509803922, + 0.9921568627, 0.9647058824, 0.9254901961, 0.9725490196078431, 0.9921568627, 0.9764705882, + 0.9490196078, 0.9764705882352941, 0.9921568627, 0.9843137255, 0.9725490196, + 0.9803921568627451, 0.9921568627, 0.9921568627, 0.9921568627, 0.984313725490196, 0.9921568627, + 0.9921568627, 0.9921568627, 0.9882352941176471, 0.9921568627, 0.9921568627, 0.9921568627, + 0.9921568627450981, 0.9921568627, 0.9921568627, 0.9921568627, 0.996078431372549, 0.9921568627, + 0.9921568627, 0.9921568627, 1.0, 0.9921568627, 0.9921568627, 0.9921568627, ], }, { ColorSpace: 'RGB', Name: 'siemens', RGBPoints: [ - 0.0, - 0.0078431373, - 0.0039215686, - 0.1254901961, - 0.00392156862745098, - 0.0078431373, - 0.0039215686, - 0.1254901961, - 0.00784313725490196, - 0.0078431373, - 0.0039215686, - 0.1882352941, - 0.011764705882352941, - 0.0117647059, - 0.0039215686, - 0.2509803922, - 0.01568627450980392, - 0.0117647059, - 0.0039215686, - 0.3098039216, - 0.0196078431372549, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.023529411764705882, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.027450980392156862, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.03137254901960784, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.03529411764705882, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.0392156862745098, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.043137254901960784, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.047058823529411764, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.050980392156862744, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.054901960784313725, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.05882352941176471, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.06274509803921569, - 0.0156862745, - 0.0039215686, - 0.3882352941, - 0.06666666666666667, - 0.0156862745, - 0.0039215686, - 0.4078431373, - 0.07058823529411765, - 0.0156862745, - 0.0039215686, - 0.4235294118, - 0.07450980392156863, - 0.0156862745, - 0.0039215686, - 0.4431372549, - 0.0784313725490196, - 0.0156862745, - 0.0039215686, - 0.462745098, - 0.08235294117647059, - 0.0156862745, - 0.0039215686, - 0.4784313725, - 0.08627450980392157, - 0.0156862745, - 0.0039215686, - 0.4980392157, - 0.09019607843137255, - 0.0196078431, - 0.0039215686, - 0.5137254902, - 0.09411764705882353, - 0.0196078431, - 0.0039215686, - 0.5333333333, - 0.09803921568627451, - 0.0196078431, - 0.0039215686, - 0.5529411765, - 0.10196078431372549, - 0.0196078431, - 0.0039215686, - 0.568627451, - 0.10588235294117647, - 0.0196078431, - 0.0039215686, - 0.5882352941, - 0.10980392156862745, - 0.0196078431, - 0.0039215686, - 0.6039215686, - 0.11372549019607843, - 0.0196078431, - 0.0039215686, - 0.6235294118, - 0.11764705882352942, - 0.0196078431, - 0.0039215686, - 0.6431372549, - 0.12156862745098039, - 0.0235294118, - 0.0039215686, - 0.6588235294, - 0.12549019607843137, - 0.0235294118, - 0.0039215686, - 0.6784313725, - 0.12941176470588237, - 0.0235294118, - 0.0039215686, - 0.6980392157, - 0.13333333333333333, - 0.0235294118, - 0.0039215686, - 0.7137254902, - 0.13725490196078433, - 0.0235294118, - 0.0039215686, - 0.7333333333, - 0.1411764705882353, - 0.0235294118, - 0.0039215686, - 0.7490196078, - 0.1450980392156863, - 0.0235294118, - 0.0039215686, - 0.7647058824, - 0.14901960784313725, - 0.0235294118, - 0.0039215686, - 0.7843137255, - 0.15294117647058825, - 0.0274509804, - 0.0039215686, - 0.8, - 0.1568627450980392, - 0.0274509804, - 0.0039215686, - 0.8196078431, - 0.1607843137254902, - 0.0274509804, - 0.0039215686, - 0.8352941176, - 0.16470588235294117, - 0.0274509804, - 0.0039215686, - 0.8549019608, - 0.16862745098039217, - 0.0274509804, - 0.0039215686, - 0.8745098039, - 0.17254901960784313, - 0.0274509804, - 0.0039215686, - 0.8901960784, - 0.17647058823529413, - 0.0274509804, - 0.0039215686, - 0.9098039216, - 0.1803921568627451, - 0.031372549, - 0.0039215686, - 0.9294117647, - 0.1843137254901961, - 0.031372549, - 0.0039215686, - 0.9254901961, - 0.18823529411764706, - 0.0509803922, - 0.0039215686, - 0.9098039216, - 0.19215686274509805, - 0.0705882353, - 0.0039215686, - 0.8901960784, - 0.19607843137254902, - 0.0901960784, - 0.0039215686, - 0.8705882353, - 0.2, - 0.1137254902, - 0.0039215686, - 0.8509803922, - 0.20392156862745098, - 0.1333333333, - 0.0039215686, - 0.831372549, - 0.20784313725490197, - 0.1529411765, - 0.0039215686, - 0.8117647059, - 0.21176470588235294, - 0.1725490196, - 0.0039215686, - 0.7921568627, - 0.21568627450980393, - 0.1960784314, - 0.0039215686, - 0.7725490196, - 0.2196078431372549, - 0.2156862745, - 0.0039215686, - 0.7529411765, - 0.2235294117647059, - 0.2352941176, - 0.0039215686, - 0.737254902, - 0.22745098039215686, - 0.2509803922, - 0.0039215686, - 0.7176470588, - 0.23137254901960785, - 0.2745098039, - 0.0039215686, - 0.6980392157, - 0.23529411764705885, - 0.2941176471, - 0.0039215686, - 0.6784313725, - 0.23921568627450984, - 0.3137254902, - 0.0039215686, - 0.6588235294, - 0.24313725490196078, - 0.3333333333, - 0.0039215686, - 0.6392156863, - 0.24705882352941178, - 0.3568627451, - 0.0039215686, - 0.6196078431, - 0.25098039215686274, - 0.3764705882, - 0.0039215686, - 0.6, - 0.2549019607843137, - 0.3960784314, - 0.0039215686, - 0.5803921569, - 0.25882352941176473, - 0.4156862745, - 0.0039215686, - 0.5607843137, - 0.2627450980392157, - 0.4392156863, - 0.0039215686, - 0.5411764706, - 0.26666666666666666, - 0.4588235294, - 0.0039215686, - 0.5215686275, - 0.27058823529411763, - 0.4784313725, - 0.0039215686, - 0.5019607843, - 0.27450980392156865, - 0.4980392157, - 0.0039215686, - 0.4823529412, - 0.2784313725490196, - 0.5215686275, - 0.0039215686, - 0.4666666667, - 0.2823529411764706, - 0.5411764706, - 0.0039215686, - 0.4470588235, - 0.28627450980392155, - 0.5607843137, - 0.0039215686, - 0.4274509804, - 0.2901960784313726, - 0.5803921569, - 0.0039215686, - 0.4078431373, - 0.29411764705882354, - 0.6039215686, - 0.0039215686, - 0.3882352941, - 0.2980392156862745, - 0.6235294118, - 0.0039215686, - 0.368627451, - 0.30196078431372547, - 0.6431372549, - 0.0039215686, - 0.3490196078, - 0.3058823529411765, - 0.662745098, - 0.0039215686, - 0.3294117647, - 0.30980392156862746, - 0.6862745098, - 0.0039215686, - 0.3098039216, - 0.3137254901960784, - 0.7058823529, - 0.0039215686, - 0.2901960784, - 0.3176470588235294, - 0.7254901961, - 0.0039215686, - 0.2705882353, - 0.3215686274509804, - 0.7450980392, - 0.0039215686, - 0.2509803922, - 0.3254901960784314, - 0.7647058824, - 0.0039215686, - 0.2352941176, - 0.32941176470588235, - 0.7843137255, - 0.0039215686, - 0.2156862745, - 0.3333333333333333, - 0.8039215686, - 0.0039215686, - 0.1960784314, - 0.33725490196078434, - 0.8235294118, - 0.0039215686, - 0.1764705882, - 0.3411764705882353, - 0.8470588235, - 0.0039215686, - 0.1568627451, - 0.34509803921568627, - 0.8666666667, - 0.0039215686, - 0.137254902, - 0.34901960784313724, - 0.8862745098, - 0.0039215686, - 0.1176470588, - 0.35294117647058826, - 0.9058823529, - 0.0039215686, - 0.0980392157, - 0.3568627450980392, - 0.9294117647, - 0.0039215686, - 0.0784313725, - 0.3607843137254902, - 0.9490196078, - 0.0039215686, - 0.0588235294, - 0.36470588235294116, - 0.968627451, - 0.0039215686, - 0.0392156863, - 0.3686274509803922, - 0.9921568627, - 0.0039215686, - 0.0235294118, - 0.37254901960784315, - 0.9529411765, - 0.0039215686, - 0.0588235294, - 0.3764705882352941, - 0.9529411765, - 0.0078431373, - 0.0549019608, - 0.3803921568627451, - 0.9529411765, - 0.0156862745, - 0.0549019608, - 0.3843137254901961, - 0.9529411765, - 0.0235294118, - 0.0549019608, - 0.38823529411764707, - 0.9529411765, - 0.031372549, - 0.0549019608, - 0.39215686274509803, - 0.9529411765, - 0.0352941176, - 0.0549019608, - 0.396078431372549, - 0.9529411765, - 0.0431372549, - 0.0549019608, - 0.4, - 0.9529411765, - 0.0509803922, - 0.0549019608, - 0.403921568627451, - 0.9529411765, - 0.0588235294, - 0.0549019608, - 0.40784313725490196, - 0.9529411765, - 0.062745098, - 0.0549019608, - 0.4117647058823529, - 0.9529411765, - 0.0705882353, - 0.0549019608, - 0.41568627450980394, - 0.9529411765, - 0.0784313725, - 0.0509803922, - 0.4196078431372549, - 0.9529411765, - 0.0862745098, - 0.0509803922, - 0.4235294117647059, - 0.9568627451, - 0.0941176471, - 0.0509803922, - 0.42745098039215684, - 0.9568627451, - 0.0980392157, - 0.0509803922, - 0.43137254901960786, - 0.9568627451, - 0.1058823529, - 0.0509803922, - 0.43529411764705883, - 0.9568627451, - 0.1137254902, - 0.0509803922, - 0.4392156862745098, - 0.9568627451, - 0.1215686275, - 0.0509803922, - 0.44313725490196076, - 0.9568627451, - 0.1254901961, - 0.0509803922, - 0.4470588235294118, - 0.9568627451, - 0.1333333333, - 0.0509803922, - 0.45098039215686275, - 0.9568627451, - 0.1411764706, - 0.0509803922, - 0.4549019607843137, - 0.9568627451, - 0.1490196078, - 0.0470588235, - 0.4588235294117647, - 0.9568627451, - 0.1568627451, - 0.0470588235, - 0.4627450980392157, - 0.9568627451, - 0.1607843137, - 0.0470588235, - 0.4666666666666667, - 0.9568627451, - 0.168627451, - 0.0470588235, - 0.4705882352941177, - 0.9607843137, - 0.1764705882, - 0.0470588235, - 0.4745098039215686, - 0.9607843137, - 0.1843137255, - 0.0470588235, - 0.4784313725490197, - 0.9607843137, - 0.1882352941, - 0.0470588235, - 0.48235294117647065, - 0.9607843137, - 0.1960784314, - 0.0470588235, - 0.48627450980392156, - 0.9607843137, - 0.2039215686, - 0.0470588235, - 0.49019607843137253, - 0.9607843137, - 0.2117647059, - 0.0470588235, - 0.49411764705882355, - 0.9607843137, - 0.2196078431, - 0.0431372549, - 0.4980392156862745, - 0.9607843137, - 0.2235294118, - 0.0431372549, - 0.5019607843137255, - 0.9607843137, - 0.231372549, - 0.0431372549, - 0.5058823529411764, - 0.9607843137, - 0.2392156863, - 0.0431372549, - 0.5098039215686274, - 0.9607843137, - 0.2470588235, - 0.0431372549, - 0.5137254901960784, - 0.9607843137, - 0.2509803922, - 0.0431372549, - 0.5176470588235295, - 0.9647058824, - 0.2549019608, - 0.0431372549, - 0.5215686274509804, - 0.9647058824, - 0.262745098, - 0.0431372549, - 0.5254901960784314, - 0.9647058824, - 0.2705882353, - 0.0431372549, - 0.5294117647058824, - 0.9647058824, - 0.2745098039, - 0.0431372549, - 0.5333333333333333, - 0.9647058824, - 0.2823529412, - 0.0392156863, - 0.5372549019607843, - 0.9647058824, - 0.2901960784, - 0.0392156863, - 0.5411764705882353, - 0.9647058824, - 0.2980392157, - 0.0392156863, - 0.5450980392156862, - 0.9647058824, - 0.3058823529, - 0.0392156863, - 0.5490196078431373, - 0.9647058824, - 0.3098039216, - 0.0392156863, - 0.5529411764705883, - 0.9647058824, - 0.3176470588, - 0.0392156863, - 0.5568627450980392, - 0.9647058824, - 0.3254901961, - 0.0392156863, - 0.5607843137254902, - 0.9647058824, - 0.3333333333, - 0.0392156863, - 0.5647058823529412, - 0.9647058824, - 0.337254902, - 0.0392156863, - 0.5686274509803921, - 0.968627451, - 0.3450980392, - 0.0392156863, - 0.5725490196078431, - 0.968627451, - 0.3529411765, - 0.0352941176, - 0.5764705882352941, - 0.968627451, - 0.3607843137, - 0.0352941176, - 0.5803921568627451, - 0.968627451, - 0.368627451, - 0.0352941176, - 0.5843137254901961, - 0.968627451, - 0.3725490196, - 0.0352941176, - 0.5882352941176471, - 0.968627451, - 0.3803921569, - 0.0352941176, - 0.592156862745098, - 0.968627451, - 0.3882352941, - 0.0352941176, - 0.596078431372549, - 0.968627451, - 0.3960784314, - 0.0352941176, - 0.6, - 0.968627451, - 0.4, - 0.0352941176, - 0.6039215686274509, - 0.968627451, - 0.4078431373, - 0.0352941176, - 0.6078431372549019, - 0.968627451, - 0.4156862745, - 0.0352941176, - 0.611764705882353, - 0.968627451, - 0.4235294118, - 0.031372549, - 0.615686274509804, - 0.9725490196, - 0.431372549, - 0.031372549, - 0.6196078431372549, - 0.9725490196, - 0.4352941176, - 0.031372549, - 0.6235294117647059, - 0.9725490196, - 0.4431372549, - 0.031372549, - 0.6274509803921569, - 0.9725490196, - 0.4509803922, - 0.031372549, - 0.6313725490196078, - 0.9725490196, - 0.4588235294, - 0.031372549, - 0.6352941176470588, - 0.9725490196, - 0.462745098, - 0.031372549, - 0.6392156862745098, - 0.9725490196, - 0.4705882353, - 0.031372549, - 0.6431372549019608, - 0.9725490196, - 0.4784313725, - 0.031372549, - 0.6470588235294118, - 0.9725490196, - 0.4862745098, - 0.031372549, - 0.6509803921568628, - 0.9725490196, - 0.4941176471, - 0.0274509804, - 0.6549019607843137, - 0.9725490196, - 0.4980392157, - 0.0274509804, - 0.6588235294117647, - 0.9725490196, - 0.5058823529, - 0.0274509804, - 0.6627450980392157, - 0.9764705882, - 0.5137254902, - 0.0274509804, - 0.6666666666666666, - 0.9764705882, - 0.5215686275, - 0.0274509804, - 0.6705882352941176, - 0.9764705882, - 0.5254901961, - 0.0274509804, - 0.6745098039215687, - 0.9764705882, - 0.5333333333, - 0.0274509804, - 0.6784313725490196, - 0.9764705882, - 0.5411764706, - 0.0274509804, - 0.6823529411764706, - 0.9764705882, - 0.5490196078, - 0.0274509804, - 0.6862745098039216, - 0.9764705882, - 0.5529411765, - 0.0274509804, - 0.6901960784313725, - 0.9764705882, - 0.5607843137, - 0.0235294118, - 0.6941176470588235, - 0.9764705882, - 0.568627451, - 0.0235294118, - 0.6980392156862745, - 0.9764705882, - 0.5764705882, - 0.0235294118, - 0.7019607843137254, - 0.9764705882, - 0.5843137255, - 0.0235294118, - 0.7058823529411765, - 0.9764705882, - 0.5882352941, - 0.0235294118, - 0.7098039215686275, - 0.9764705882, - 0.5960784314, - 0.0235294118, - 0.7137254901960784, - 0.9803921569, - 0.6039215686, - 0.0235294118, - 0.7176470588235294, - 0.9803921569, - 0.6117647059, - 0.0235294118, - 0.7215686274509804, - 0.9803921569, - 0.6156862745, - 0.0235294118, - 0.7254901960784313, - 0.9803921569, - 0.6235294118, - 0.0235294118, - 0.7294117647058823, - 0.9803921569, - 0.631372549, - 0.0196078431, - 0.7333333333333333, - 0.9803921569, - 0.6392156863, - 0.0196078431, - 0.7372549019607844, - 0.9803921569, - 0.6470588235, - 0.0196078431, - 0.7411764705882353, - 0.9803921569, - 0.6509803922, - 0.0196078431, - 0.7450980392156863, - 0.9803921569, - 0.6588235294, - 0.0196078431, - 0.7490196078431373, - 0.9803921569, - 0.6666666667, - 0.0196078431, - 0.7529411764705882, - 0.9803921569, - 0.6745098039, - 0.0196078431, - 0.7568627450980392, - 0.9803921569, - 0.6784313725, - 0.0196078431, - 0.7607843137254902, - 0.9843137255, - 0.6862745098, - 0.0196078431, - 0.7647058823529411, - 0.9843137255, - 0.6941176471, - 0.0196078431, - 0.7686274509803922, - 0.9843137255, - 0.7019607843, - 0.0156862745, - 0.7725490196078432, - 0.9843137255, - 0.7098039216, - 0.0156862745, - 0.7764705882352941, - 0.9843137255, - 0.7137254902, - 0.0156862745, - 0.7803921568627451, - 0.9843137255, - 0.7215686275, - 0.0156862745, - 0.7843137254901961, - 0.9843137255, - 0.7294117647, - 0.0156862745, - 0.788235294117647, - 0.9843137255, - 0.737254902, - 0.0156862745, - 0.792156862745098, - 0.9843137255, - 0.7411764706, - 0.0156862745, - 0.796078431372549, - 0.9843137255, - 0.7490196078, - 0.0156862745, - 0.8, - 0.9843137255, - 0.7529411765, - 0.0156862745, - 0.803921568627451, - 0.9843137255, - 0.7607843137, - 0.0156862745, - 0.807843137254902, - 0.9882352941, - 0.768627451, - 0.0156862745, - 0.8117647058823529, - 0.9882352941, - 0.768627451, - 0.0156862745, - 0.8156862745098039, - 0.9843137255, - 0.7843137255, - 0.0117647059, - 0.8196078431372549, - 0.9843137255, - 0.8, - 0.0117647059, - 0.8235294117647058, - 0.9843137255, - 0.8156862745, - 0.0117647059, - 0.8274509803921568, - 0.9803921569, - 0.831372549, - 0.0117647059, - 0.8313725490196079, - 0.9803921569, - 0.8431372549, - 0.0117647059, - 0.8352941176470589, - 0.9803921569, - 0.8588235294, - 0.0078431373, - 0.8392156862745098, - 0.9803921569, - 0.8745098039, - 0.0078431373, - 0.8431372549019608, - 0.9764705882, - 0.8901960784, - 0.0078431373, - 0.8470588235294118, - 0.9764705882, - 0.9058823529, - 0.0078431373, - 0.8509803921568627, - 0.9764705882, - 0.9176470588, - 0.0078431373, - 0.8549019607843137, - 0.9764705882, - 0.9333333333, - 0.0039215686, - 0.8588235294117647, - 0.9725490196, - 0.9490196078, - 0.0039215686, - 0.8627450980392157, - 0.9725490196, - 0.9647058824, - 0.0039215686, - 0.8666666666666667, - 0.9725490196, - 0.9803921569, - 0.0039215686, - 0.8705882352941177, - 0.9725490196, - 0.9960784314, - 0.0039215686, - 0.8745098039215686, - 0.9725490196, - 0.9960784314, - 0.0039215686, - 0.8784313725490196, - 0.9725490196, - 0.9960784314, - 0.0352941176, - 0.8823529411764706, - 0.9725490196, - 0.9960784314, - 0.0666666667, - 0.8862745098039215, - 0.9725490196, - 0.9960784314, - 0.0980392157, - 0.8901960784313725, - 0.9725490196, - 0.9960784314, - 0.1294117647, - 0.8941176470588236, - 0.9725490196, - 0.9960784314, - 0.1647058824, - 0.8980392156862745, - 0.9764705882, - 0.9960784314, - 0.1960784314, - 0.9019607843137255, - 0.9764705882, - 0.9960784314, - 0.2274509804, - 0.9058823529411765, - 0.9764705882, - 0.9960784314, - 0.2549019608, - 0.9098039215686274, - 0.9764705882, - 0.9960784314, - 0.2901960784, - 0.9137254901960784, - 0.9764705882, - 0.9960784314, - 0.3215686275, - 0.9176470588235294, - 0.9803921569, - 0.9960784314, - 0.3529411765, - 0.9215686274509803, - 0.9803921569, - 0.9960784314, - 0.3843137255, - 0.9254901960784314, - 0.9803921569, - 0.9960784314, - 0.4156862745, - 0.9294117647058824, - 0.9803921569, - 0.9960784314, - 0.4509803922, - 0.9333333333333333, - 0.9803921569, - 0.9960784314, - 0.4823529412, - 0.9372549019607843, - 0.9843137255, - 0.9960784314, - 0.5137254902, - 0.9411764705882354, - 0.9843137255, - 0.9960784314, - 0.5450980392, - 0.9450980392156864, - 0.9843137255, - 0.9960784314, - 0.5803921569, - 0.9490196078431372, - 0.9843137255, - 0.9960784314, - 0.6117647059, - 0.9529411764705882, - 0.9843137255, - 0.9960784314, - 0.6431372549, - 0.9568627450980394, - 0.9882352941, - 0.9960784314, - 0.6745098039, - 0.9607843137254903, - 0.9882352941, - 0.9960784314, - 0.7058823529, - 0.9647058823529413, - 0.9882352941, - 0.9960784314, - 0.7411764706, - 0.9686274509803922, - 0.9882352941, - 0.9960784314, - 0.768627451, - 0.9725490196078431, - 0.9882352941, - 0.9960784314, - 0.8, - 0.9764705882352941, - 0.9921568627, - 0.9960784314, - 0.831372549, - 0.9803921568627451, - 0.9921568627, - 0.9960784314, - 0.8666666667, - 0.984313725490196, - 0.9921568627, - 0.9960784314, - 0.8980392157, - 0.9882352941176471, - 0.9921568627, - 0.9960784314, - 0.9294117647, - 0.9921568627450981, - 0.9921568627, - 0.9960784314, - 0.9607843137, - 0.996078431372549, - 0.9960784314, - 0.9960784314, - 0.9607843137, - 1.0, - 0.9960784314, - 0.9960784314, - 0.9607843137, + 0.0, 0.0078431373, 0.0039215686, 0.1254901961, 0.00392156862745098, 0.0078431373, + 0.0039215686, 0.1254901961, 0.00784313725490196, 0.0078431373, 0.0039215686, 0.1882352941, + 0.011764705882352941, 0.0117647059, 0.0039215686, 0.2509803922, 0.01568627450980392, + 0.0117647059, 0.0039215686, 0.3098039216, 0.0196078431372549, 0.0156862745, 0.0039215686, + 0.3725490196, 0.023529411764705882, 0.0156862745, 0.0039215686, 0.3725490196, + 0.027450980392156862, 0.0156862745, 0.0039215686, 0.3725490196, 0.03137254901960784, + 0.0156862745, 0.0039215686, 0.3725490196, 0.03529411764705882, 0.0156862745, 0.0039215686, + 0.3725490196, 0.0392156862745098, 0.0156862745, 0.0039215686, 0.3725490196, + 0.043137254901960784, 0.0156862745, 0.0039215686, 0.3725490196, 0.047058823529411764, + 0.0156862745, 0.0039215686, 0.3725490196, 0.050980392156862744, 0.0156862745, 0.0039215686, + 0.3725490196, 0.054901960784313725, 0.0156862745, 0.0039215686, 0.3725490196, + 0.05882352941176471, 0.0156862745, 0.0039215686, 0.3725490196, 0.06274509803921569, + 0.0156862745, 0.0039215686, 0.3882352941, 0.06666666666666667, 0.0156862745, 0.0039215686, + 0.4078431373, 0.07058823529411765, 0.0156862745, 0.0039215686, 0.4235294118, + 0.07450980392156863, 0.0156862745, 0.0039215686, 0.4431372549, 0.0784313725490196, + 0.0156862745, 0.0039215686, 0.462745098, 0.08235294117647059, 0.0156862745, 0.0039215686, + 0.4784313725, 0.08627450980392157, 0.0156862745, 0.0039215686, 0.4980392157, + 0.09019607843137255, 0.0196078431, 0.0039215686, 0.5137254902, 0.09411764705882353, + 0.0196078431, 0.0039215686, 0.5333333333, 0.09803921568627451, 0.0196078431, 0.0039215686, + 0.5529411765, 0.10196078431372549, 0.0196078431, 0.0039215686, 0.568627451, + 0.10588235294117647, 0.0196078431, 0.0039215686, 0.5882352941, 0.10980392156862745, + 0.0196078431, 0.0039215686, 0.6039215686, 0.11372549019607843, 0.0196078431, 0.0039215686, + 0.6235294118, 0.11764705882352942, 0.0196078431, 0.0039215686, 0.6431372549, + 0.12156862745098039, 0.0235294118, 0.0039215686, 0.6588235294, 0.12549019607843137, + 0.0235294118, 0.0039215686, 0.6784313725, 0.12941176470588237, 0.0235294118, 0.0039215686, + 0.6980392157, 0.13333333333333333, 0.0235294118, 0.0039215686, 0.7137254902, + 0.13725490196078433, 0.0235294118, 0.0039215686, 0.7333333333, 0.1411764705882353, + 0.0235294118, 0.0039215686, 0.7490196078, 0.1450980392156863, 0.0235294118, 0.0039215686, + 0.7647058824, 0.14901960784313725, 0.0235294118, 0.0039215686, 0.7843137255, + 0.15294117647058825, 0.0274509804, 0.0039215686, 0.8, 0.1568627450980392, 0.0274509804, + 0.0039215686, 0.8196078431, 0.1607843137254902, 0.0274509804, 0.0039215686, 0.8352941176, + 0.16470588235294117, 0.0274509804, 0.0039215686, 0.8549019608, 0.16862745098039217, + 0.0274509804, 0.0039215686, 0.8745098039, 0.17254901960784313, 0.0274509804, 0.0039215686, + 0.8901960784, 0.17647058823529413, 0.0274509804, 0.0039215686, 0.9098039216, + 0.1803921568627451, 0.031372549, 0.0039215686, 0.9294117647, 0.1843137254901961, 0.031372549, + 0.0039215686, 0.9254901961, 0.18823529411764706, 0.0509803922, 0.0039215686, 0.9098039216, + 0.19215686274509805, 0.0705882353, 0.0039215686, 0.8901960784, 0.19607843137254902, + 0.0901960784, 0.0039215686, 0.8705882353, 0.2, 0.1137254902, 0.0039215686, 0.8509803922, + 0.20392156862745098, 0.1333333333, 0.0039215686, 0.831372549, 0.20784313725490197, + 0.1529411765, 0.0039215686, 0.8117647059, 0.21176470588235294, 0.1725490196, 0.0039215686, + 0.7921568627, 0.21568627450980393, 0.1960784314, 0.0039215686, 0.7725490196, + 0.2196078431372549, 0.2156862745, 0.0039215686, 0.7529411765, 0.2235294117647059, + 0.2352941176, 0.0039215686, 0.737254902, 0.22745098039215686, 0.2509803922, 0.0039215686, + 0.7176470588, 0.23137254901960785, 0.2745098039, 0.0039215686, 0.6980392157, + 0.23529411764705885, 0.2941176471, 0.0039215686, 0.6784313725, 0.23921568627450984, + 0.3137254902, 0.0039215686, 0.6588235294, 0.24313725490196078, 0.3333333333, 0.0039215686, + 0.6392156863, 0.24705882352941178, 0.3568627451, 0.0039215686, 0.6196078431, + 0.25098039215686274, 0.3764705882, 0.0039215686, 0.6, 0.2549019607843137, 0.3960784314, + 0.0039215686, 0.5803921569, 0.25882352941176473, 0.4156862745, 0.0039215686, 0.5607843137, + 0.2627450980392157, 0.4392156863, 0.0039215686, 0.5411764706, 0.26666666666666666, + 0.4588235294, 0.0039215686, 0.5215686275, 0.27058823529411763, 0.4784313725, 0.0039215686, + 0.5019607843, 0.27450980392156865, 0.4980392157, 0.0039215686, 0.4823529412, + 0.2784313725490196, 0.5215686275, 0.0039215686, 0.4666666667, 0.2823529411764706, + 0.5411764706, 0.0039215686, 0.4470588235, 0.28627450980392155, 0.5607843137, 0.0039215686, + 0.4274509804, 0.2901960784313726, 0.5803921569, 0.0039215686, 0.4078431373, + 0.29411764705882354, 0.6039215686, 0.0039215686, 0.3882352941, 0.2980392156862745, + 0.6235294118, 0.0039215686, 0.368627451, 0.30196078431372547, 0.6431372549, 0.0039215686, + 0.3490196078, 0.3058823529411765, 0.662745098, 0.0039215686, 0.3294117647, + 0.30980392156862746, 0.6862745098, 0.0039215686, 0.3098039216, 0.3137254901960784, + 0.7058823529, 0.0039215686, 0.2901960784, 0.3176470588235294, 0.7254901961, 0.0039215686, + 0.2705882353, 0.3215686274509804, 0.7450980392, 0.0039215686, 0.2509803922, + 0.3254901960784314, 0.7647058824, 0.0039215686, 0.2352941176, 0.32941176470588235, + 0.7843137255, 0.0039215686, 0.2156862745, 0.3333333333333333, 0.8039215686, 0.0039215686, + 0.1960784314, 0.33725490196078434, 0.8235294118, 0.0039215686, 0.1764705882, + 0.3411764705882353, 0.8470588235, 0.0039215686, 0.1568627451, 0.34509803921568627, + 0.8666666667, 0.0039215686, 0.137254902, 0.34901960784313724, 0.8862745098, 0.0039215686, + 0.1176470588, 0.35294117647058826, 0.9058823529, 0.0039215686, 0.0980392157, + 0.3568627450980392, 0.9294117647, 0.0039215686, 0.0784313725, 0.3607843137254902, + 0.9490196078, 0.0039215686, 0.0588235294, 0.36470588235294116, 0.968627451, 0.0039215686, + 0.0392156863, 0.3686274509803922, 0.9921568627, 0.0039215686, 0.0235294118, + 0.37254901960784315, 0.9529411765, 0.0039215686, 0.0588235294, 0.3764705882352941, + 0.9529411765, 0.0078431373, 0.0549019608, 0.3803921568627451, 0.9529411765, 0.0156862745, + 0.0549019608, 0.3843137254901961, 0.9529411765, 0.0235294118, 0.0549019608, + 0.38823529411764707, 0.9529411765, 0.031372549, 0.0549019608, 0.39215686274509803, + 0.9529411765, 0.0352941176, 0.0549019608, 0.396078431372549, 0.9529411765, 0.0431372549, + 0.0549019608, 0.4, 0.9529411765, 0.0509803922, 0.0549019608, 0.403921568627451, 0.9529411765, + 0.0588235294, 0.0549019608, 0.40784313725490196, 0.9529411765, 0.062745098, 0.0549019608, + 0.4117647058823529, 0.9529411765, 0.0705882353, 0.0549019608, 0.41568627450980394, + 0.9529411765, 0.0784313725, 0.0509803922, 0.4196078431372549, 0.9529411765, 0.0862745098, + 0.0509803922, 0.4235294117647059, 0.9568627451, 0.0941176471, 0.0509803922, + 0.42745098039215684, 0.9568627451, 0.0980392157, 0.0509803922, 0.43137254901960786, + 0.9568627451, 0.1058823529, 0.0509803922, 0.43529411764705883, 0.9568627451, 0.1137254902, + 0.0509803922, 0.4392156862745098, 0.9568627451, 0.1215686275, 0.0509803922, + 0.44313725490196076, 0.9568627451, 0.1254901961, 0.0509803922, 0.4470588235294118, + 0.9568627451, 0.1333333333, 0.0509803922, 0.45098039215686275, 0.9568627451, 0.1411764706, + 0.0509803922, 0.4549019607843137, 0.9568627451, 0.1490196078, 0.0470588235, + 0.4588235294117647, 0.9568627451, 0.1568627451, 0.0470588235, 0.4627450980392157, + 0.9568627451, 0.1607843137, 0.0470588235, 0.4666666666666667, 0.9568627451, 0.168627451, + 0.0470588235, 0.4705882352941177, 0.9607843137, 0.1764705882, 0.0470588235, + 0.4745098039215686, 0.9607843137, 0.1843137255, 0.0470588235, 0.4784313725490197, + 0.9607843137, 0.1882352941, 0.0470588235, 0.48235294117647065, 0.9607843137, 0.1960784314, + 0.0470588235, 0.48627450980392156, 0.9607843137, 0.2039215686, 0.0470588235, + 0.49019607843137253, 0.9607843137, 0.2117647059, 0.0470588235, 0.49411764705882355, + 0.9607843137, 0.2196078431, 0.0431372549, 0.4980392156862745, 0.9607843137, 0.2235294118, + 0.0431372549, 0.5019607843137255, 0.9607843137, 0.231372549, 0.0431372549, 0.5058823529411764, + 0.9607843137, 0.2392156863, 0.0431372549, 0.5098039215686274, 0.9607843137, 0.2470588235, + 0.0431372549, 0.5137254901960784, 0.9607843137, 0.2509803922, 0.0431372549, + 0.5176470588235295, 0.9647058824, 0.2549019608, 0.0431372549, 0.5215686274509804, + 0.9647058824, 0.262745098, 0.0431372549, 0.5254901960784314, 0.9647058824, 0.2705882353, + 0.0431372549, 0.5294117647058824, 0.9647058824, 0.2745098039, 0.0431372549, + 0.5333333333333333, 0.9647058824, 0.2823529412, 0.0392156863, 0.5372549019607843, + 0.9647058824, 0.2901960784, 0.0392156863, 0.5411764705882353, 0.9647058824, 0.2980392157, + 0.0392156863, 0.5450980392156862, 0.9647058824, 0.3058823529, 0.0392156863, + 0.5490196078431373, 0.9647058824, 0.3098039216, 0.0392156863, 0.5529411764705883, + 0.9647058824, 0.3176470588, 0.0392156863, 0.5568627450980392, 0.9647058824, 0.3254901961, + 0.0392156863, 0.5607843137254902, 0.9647058824, 0.3333333333, 0.0392156863, + 0.5647058823529412, 0.9647058824, 0.337254902, 0.0392156863, 0.5686274509803921, 0.968627451, + 0.3450980392, 0.0392156863, 0.5725490196078431, 0.968627451, 0.3529411765, 0.0352941176, + 0.5764705882352941, 0.968627451, 0.3607843137, 0.0352941176, 0.5803921568627451, 0.968627451, + 0.368627451, 0.0352941176, 0.5843137254901961, 0.968627451, 0.3725490196, 0.0352941176, + 0.5882352941176471, 0.968627451, 0.3803921569, 0.0352941176, 0.592156862745098, 0.968627451, + 0.3882352941, 0.0352941176, 0.596078431372549, 0.968627451, 0.3960784314, 0.0352941176, 0.6, + 0.968627451, 0.4, 0.0352941176, 0.6039215686274509, 0.968627451, 0.4078431373, 0.0352941176, + 0.6078431372549019, 0.968627451, 0.4156862745, 0.0352941176, 0.611764705882353, 0.968627451, + 0.4235294118, 0.031372549, 0.615686274509804, 0.9725490196, 0.431372549, 0.031372549, + 0.6196078431372549, 0.9725490196, 0.4352941176, 0.031372549, 0.6235294117647059, 0.9725490196, + 0.4431372549, 0.031372549, 0.6274509803921569, 0.9725490196, 0.4509803922, 0.031372549, + 0.6313725490196078, 0.9725490196, 0.4588235294, 0.031372549, 0.6352941176470588, 0.9725490196, + 0.462745098, 0.031372549, 0.6392156862745098, 0.9725490196, 0.4705882353, 0.031372549, + 0.6431372549019608, 0.9725490196, 0.4784313725, 0.031372549, 0.6470588235294118, 0.9725490196, + 0.4862745098, 0.031372549, 0.6509803921568628, 0.9725490196, 0.4941176471, 0.0274509804, + 0.6549019607843137, 0.9725490196, 0.4980392157, 0.0274509804, 0.6588235294117647, + 0.9725490196, 0.5058823529, 0.0274509804, 0.6627450980392157, 0.9764705882, 0.5137254902, + 0.0274509804, 0.6666666666666666, 0.9764705882, 0.5215686275, 0.0274509804, + 0.6705882352941176, 0.9764705882, 0.5254901961, 0.0274509804, 0.6745098039215687, + 0.9764705882, 0.5333333333, 0.0274509804, 0.6784313725490196, 0.9764705882, 0.5411764706, + 0.0274509804, 0.6823529411764706, 0.9764705882, 0.5490196078, 0.0274509804, + 0.6862745098039216, 0.9764705882, 0.5529411765, 0.0274509804, 0.6901960784313725, + 0.9764705882, 0.5607843137, 0.0235294118, 0.6941176470588235, 0.9764705882, 0.568627451, + 0.0235294118, 0.6980392156862745, 0.9764705882, 0.5764705882, 0.0235294118, + 0.7019607843137254, 0.9764705882, 0.5843137255, 0.0235294118, 0.7058823529411765, + 0.9764705882, 0.5882352941, 0.0235294118, 0.7098039215686275, 0.9764705882, 0.5960784314, + 0.0235294118, 0.7137254901960784, 0.9803921569, 0.6039215686, 0.0235294118, + 0.7176470588235294, 0.9803921569, 0.6117647059, 0.0235294118, 0.7215686274509804, + 0.9803921569, 0.6156862745, 0.0235294118, 0.7254901960784313, 0.9803921569, 0.6235294118, + 0.0235294118, 0.7294117647058823, 0.9803921569, 0.631372549, 0.0196078431, 0.7333333333333333, + 0.9803921569, 0.6392156863, 0.0196078431, 0.7372549019607844, 0.9803921569, 0.6470588235, + 0.0196078431, 0.7411764705882353, 0.9803921569, 0.6509803922, 0.0196078431, + 0.7450980392156863, 0.9803921569, 0.6588235294, 0.0196078431, 0.7490196078431373, + 0.9803921569, 0.6666666667, 0.0196078431, 0.7529411764705882, 0.9803921569, 0.6745098039, + 0.0196078431, 0.7568627450980392, 0.9803921569, 0.6784313725, 0.0196078431, + 0.7607843137254902, 0.9843137255, 0.6862745098, 0.0196078431, 0.7647058823529411, + 0.9843137255, 0.6941176471, 0.0196078431, 0.7686274509803922, 0.9843137255, 0.7019607843, + 0.0156862745, 0.7725490196078432, 0.9843137255, 0.7098039216, 0.0156862745, + 0.7764705882352941, 0.9843137255, 0.7137254902, 0.0156862745, 0.7803921568627451, + 0.9843137255, 0.7215686275, 0.0156862745, 0.7843137254901961, 0.9843137255, 0.7294117647, + 0.0156862745, 0.788235294117647, 0.9843137255, 0.737254902, 0.0156862745, 0.792156862745098, + 0.9843137255, 0.7411764706, 0.0156862745, 0.796078431372549, 0.9843137255, 0.7490196078, + 0.0156862745, 0.8, 0.9843137255, 0.7529411765, 0.0156862745, 0.803921568627451, 0.9843137255, + 0.7607843137, 0.0156862745, 0.807843137254902, 0.9882352941, 0.768627451, 0.0156862745, + 0.8117647058823529, 0.9882352941, 0.768627451, 0.0156862745, 0.8156862745098039, 0.9843137255, + 0.7843137255, 0.0117647059, 0.8196078431372549, 0.9843137255, 0.8, 0.0117647059, + 0.8235294117647058, 0.9843137255, 0.8156862745, 0.0117647059, 0.8274509803921568, + 0.9803921569, 0.831372549, 0.0117647059, 0.8313725490196079, 0.9803921569, 0.8431372549, + 0.0117647059, 0.8352941176470589, 0.9803921569, 0.8588235294, 0.0078431373, + 0.8392156862745098, 0.9803921569, 0.8745098039, 0.0078431373, 0.8431372549019608, + 0.9764705882, 0.8901960784, 0.0078431373, 0.8470588235294118, 0.9764705882, 0.9058823529, + 0.0078431373, 0.8509803921568627, 0.9764705882, 0.9176470588, 0.0078431373, + 0.8549019607843137, 0.9764705882, 0.9333333333, 0.0039215686, 0.8588235294117647, + 0.9725490196, 0.9490196078, 0.0039215686, 0.8627450980392157, 0.9725490196, 0.9647058824, + 0.0039215686, 0.8666666666666667, 0.9725490196, 0.9803921569, 0.0039215686, + 0.8705882352941177, 0.9725490196, 0.9960784314, 0.0039215686, 0.8745098039215686, + 0.9725490196, 0.9960784314, 0.0039215686, 0.8784313725490196, 0.9725490196, 0.9960784314, + 0.0352941176, 0.8823529411764706, 0.9725490196, 0.9960784314, 0.0666666667, + 0.8862745098039215, 0.9725490196, 0.9960784314, 0.0980392157, 0.8901960784313725, + 0.9725490196, 0.9960784314, 0.1294117647, 0.8941176470588236, 0.9725490196, 0.9960784314, + 0.1647058824, 0.8980392156862745, 0.9764705882, 0.9960784314, 0.1960784314, + 0.9019607843137255, 0.9764705882, 0.9960784314, 0.2274509804, 0.9058823529411765, + 0.9764705882, 0.9960784314, 0.2549019608, 0.9098039215686274, 0.9764705882, 0.9960784314, + 0.2901960784, 0.9137254901960784, 0.9764705882, 0.9960784314, 0.3215686275, + 0.9176470588235294, 0.9803921569, 0.9960784314, 0.3529411765, 0.9215686274509803, + 0.9803921569, 0.9960784314, 0.3843137255, 0.9254901960784314, 0.9803921569, 0.9960784314, + 0.4156862745, 0.9294117647058824, 0.9803921569, 0.9960784314, 0.4509803922, + 0.9333333333333333, 0.9803921569, 0.9960784314, 0.4823529412, 0.9372549019607843, + 0.9843137255, 0.9960784314, 0.5137254902, 0.9411764705882354, 0.9843137255, 0.9960784314, + 0.5450980392, 0.9450980392156864, 0.9843137255, 0.9960784314, 0.5803921569, + 0.9490196078431372, 0.9843137255, 0.9960784314, 0.6117647059, 0.9529411764705882, + 0.9843137255, 0.9960784314, 0.6431372549, 0.9568627450980394, 0.9882352941, 0.9960784314, + 0.6745098039, 0.9607843137254903, 0.9882352941, 0.9960784314, 0.7058823529, + 0.9647058823529413, 0.9882352941, 0.9960784314, 0.7411764706, 0.9686274509803922, + 0.9882352941, 0.9960784314, 0.768627451, 0.9725490196078431, 0.9882352941, 0.9960784314, 0.8, + 0.9764705882352941, 0.9921568627, 0.9960784314, 0.831372549, 0.9803921568627451, 0.9921568627, + 0.9960784314, 0.8666666667, 0.984313725490196, 0.9921568627, 0.9960784314, 0.8980392157, + 0.9882352941176471, 0.9921568627, 0.9960784314, 0.9294117647, 0.9921568627450981, + 0.9921568627, 0.9960784314, 0.9607843137, 0.996078431372549, 0.9960784314, 0.9960784314, + 0.9607843137, 1.0, 0.9960784314, 0.9960784314, 0.9607843137, ], }, ]; diff --git a/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js index 996ebd0f50f..b3678729315 100644 --- a/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js +++ b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js @@ -1,7 +1,4 @@ -export default function createAndDownloadTMTVReport( - segReport, - additionalReportRows -) { +export default function createAndDownloadTMTVReport(segReport, additionalReportRows) { const firstReport = segReport[Object.keys(segReport)[0]]; const columns = Object.keys(firstReport); const csv = [columns.join(',')]; @@ -11,9 +8,7 @@ export default function createAndDownloadTMTVReport( columns.forEach(column => { // if it is array then we need to replace , with space to avoid csv parsing error row.push( - Array.isArray(segmentation[column]) - ? segmentation[column].join(' ') - : segmentation[column] + Array.isArray(segmentation[column]) ? segmentation[column].join(' ') : segmentation[column] ); }); csv.push(row.join(',')); diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js deleted file mode 100644 index 147b0cbf606..00000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js +++ /dev/null @@ -1,262 +0,0 @@ -import AnnotationToPointData from './measurements/AnnotationToPointData'; -import dcmjs from 'dcmjs'; -import { DicomMetadataStore } from '@ohif/core'; - -const { DicomMetaDictionary } = dcmjs.data; - -export default class RTSSReport { - constructor() {} - - /** - * Convert handles to RTSSReport report object containing the dcmjs dicom dataset. - * - * Note: The tool data needs to be formatted in a specific way, and currently - * it is limited to the RectangleROIStartEndTool in the Cornerstone. - * - * @param annotations Array of Cornerstone tool annotation data - * @param metadataProvider Metadata provider - * @param options report generation options - * @returns Report object containing the dataset - */ - static generateReport(annotations, metadataProvider, options) { - let dataset = initializeDataset(annotations, metadataProvider); - - annotations.forEach((annotation, index) => { - const ContourSequence = AnnotationToPointData.convert( - annotation, - index, - metadataProvider, - options - ); - - dataset.StructureSetROISequence.push( - getStructureSetModule(annotation, index, metadataProvider) - ); - - dataset.ROIContourSequence.push(ContourSequence); - dataset.RTROIObservationsSequence.push( - getRTROIObservationsSequence(annotation, index, metadataProvider) - ); - - // ReferencedSeriesSequence - // Todo: handle more than one series - dataset.ReferencedSeriesSequence = getReferencedSeriesSequence( - annotation, - index, - metadataProvider - ); - - // ReferencedFrameOfReferenceSequence - dataset.ReferencedFrameOfReferenceSequence = getReferencedFrameOfReferenceSequence( - annotation, - metadataProvider, - dataset - ); - }); - - const fileMetaInformationVersionArray = new Uint8Array(2); - fileMetaInformationVersionArray[1] = 1; - - const _meta = { - FileMetaInformationVersion: { - Value: [fileMetaInformationVersionArray.buffer], - vr: 'OB', - }, - TransferSyntaxUID: { - Value: ['1.2.840.10008.1.2.1'], - vr: 'UI', - }, - ImplementationClassUID: { - Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id - vr: 'UI', - }, - ImplementationVersionName: { - Value: ['dcmjs'], - vr: 'SH', - }, - }; - - dataset._meta = _meta; - - return dataset; - } - - /** - * Generate Cornerstone tool state from dataset - * @param {object} dataset dataset - * @param {object} hooks - * @param {function} hooks.getToolClass Function to map dataset to a tool class - * @returns - */ - static generateToolState(dataset, hooks = {}) { - // Todo - console.warn('RTSSReport.generateToolState not implemented'); - } -} - -function initializeDataset(annotations, metadataProvider) { - const rtSOPInstanceUID = DicomMetaDictionary.uid(); - - // get the first annotation data - const { - referencedImageId: imageId, - FrameOfReferenceUID, - } = annotations[0].metadata; - - const { studyInstanceUID } = metadataProvider.get( - 'generalSeriesModule', - imageId - ); - - const patientModule = getPatientModule(imageId, metadataProvider); - const rtSeriesModule = getRTSeriesModule(imageId, metadataProvider); - - return { - StructureSetROISequence: [], - ROIContourSequence: [], - RTROIObservationsSequence: [], - ReferencedSeriesSequence: [], - ReferencedFrameOfReferenceSequence: [], - ...patientModule, - ...rtSeriesModule, - StudyInstanceUID: studyInstanceUID, - SOPClassUID: '1.2.840.10008.5.1.4.1.1.481.3', // RT Structure Set Storage - SOPInstanceUID: rtSOPInstanceUID, - Manufacturer: 'dcmjs', - Modality: 'RTSTRUCT', - FrameOfReferenceUID, - PositionReferenceIndicator: '', - StructureSetLabel: '', - StructureSetName: '', - ReferringPhysicianName: '', - OperatorsName: '', - StructureSetDate: DicomMetaDictionary.date(), - StructureSetTime: DicomMetaDictionary.time(), - }; -} - -function getPatientModule(imageId, metadataProvider) { - const generalSeriesModule = metadataProvider.get( - 'generalSeriesModule', - imageId - ); - const generalStudyModule = metadataProvider.get( - 'generalStudyModule', - imageId - ); - const patientStudyModule = metadataProvider.get( - 'patientStudyModule', - imageId - ); - const patientModule = metadataProvider.get('patientModule', imageId); - const patientDemographicModule = metadataProvider.get( - 'patientDemographicModule', - imageId - ); - - return { - Modality: generalSeriesModule.modality, - PatientID: patientModule.patientId, - PatientName: patientModule.patientName, - PatientBirthDate: '', - PatientAge: patientStudyModule.patientAge, - PatientSex: patientDemographicModule.patientSex, - PatientWeight: patientStudyModule.patientWeight, - StudyDate: generalStudyModule.studyDate, - StudyTime: generalStudyModule.studyTime, - StudyID: 'ToDo', - AccessionNumber: generalStudyModule.accessionNumber, - }; -} - -function getReferencedFrameOfReferenceSequence( - toolData, - metadataProvider, - dataset -) { - const { referencedImageId: imageId, FrameOfReferenceUID } = toolData.metadata; - const instance = metadataProvider.get('instance', imageId); - const { SeriesInstanceUID } = instance; - - const { ReferencedSeriesSequence } = dataset; - - return [ - { - FrameOfReferenceUID, - RTReferencedStudySequence: [ - { - ReferencedSOPClassUID: dataset.SOPClassUID, - ReferencedSOPInstanceUID: dataset.SOPInstanceUID, - RTReferencedSeriesSequence: [ - { - SeriesInstanceUID, - ContourImageSequence: [ - ...ReferencedSeriesSequence[0].ReferencedInstanceSequence, - ], - }, - ], - }, - ], - }, - ]; -} - -function getReferencedSeriesSequence(toolData, index, metadataProvider) { - // grab imageId from toolData - const { referencedImageId: imageId } = toolData.metadata; - const instance = metadataProvider.get('instance', imageId); - const { SeriesInstanceUID, StudyInstanceUID } = instance; - - const ReferencedSeriesSequence = []; - if (SeriesInstanceUID) { - const series = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - - const ReferencedSeries = { - SeriesInstanceUID, - ReferencedInstanceSequence: [], - }; - - series.instances.forEach(instance => { - const { SOPInstanceUID, SOPClassUID } = instance; - ReferencedSeries.ReferencedInstanceSequence.push({ - ReferencedSOPClassUID: SOPClassUID, - ReferencedSOPInstanceUID: SOPInstanceUID, - }); - }); - - ReferencedSeriesSequence.push(ReferencedSeries); - } - - return ReferencedSeriesSequence; -} - -function getRTSeriesModule(imageId, metadataProvider) { - return { - SeriesInstanceUID: DicomMetaDictionary.uid(), // generate a new series instance uid - SeriesNumber: '99', // Todo:: what should be the series number? - }; -} - -function getStructureSetModule(toolData, index, metadataProvider) { - const { FrameOfReferenceUID } = toolData.metadata; - - return { - ROINumber: index + 1, - ROIName: `Todo: name ${index + 1}`, - ROIDescription: `Todo: description ${index + 1}`, - ROIGenerationAlgorithm: 'Todo: algorithm', - ReferencedFrameOfReferenceUID: FrameOfReferenceUID, - }; -} - -function getRTROIObservationsSequence(toolData, index, metadataProvider) { - return { - ObservationNumber: index + 1, - ReferencedROINumber: index + 1, - RTROIInterpretedType: 'Todo: type', - ROIInterpreter: 'Todo: interpreter', - }; -} diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js index 999b37addaf..df4aa9c3cc1 100644 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js @@ -1,12 +1,16 @@ -import RTSSReport from './RTSSReport'; import dcmjs from 'dcmjs'; -import { classes } from '@ohif/core'; +import { classes, DicomMetadataStore } from '@ohif/core'; +import { adaptersSEG } from '@cornerstonejs/adapters'; const { datasetToBlob } = dcmjs.data; const metadataProvider = classes.MetadataProvider; export default function dicomRTAnnotationExport(annotations) { - const dataset = RTSSReport.generateReport(annotations, metadataProvider); + const dataset = adaptersSEG.Cornerstone3D.RTStruct.RTSS.generateRTSSFromAnnotations( + annotations, + metadataProvider, + DicomMetadataStore + ); const reportBlob = datasetToBlob(dataset); //Create a URL for the binary. diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js deleted file mode 100644 index f74fdc72ae7..00000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js +++ /dev/null @@ -1,58 +0,0 @@ -import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold'; - -function validateAnnotation(annotation) { - if (!annotation?.data) { - throw new Error('Tool data is empty'); - } - - if (!annotation.metadata || annotation.metadata.referenceImageId) { - throw new Error('Tool data is not associated with any imageId'); - } -} - -class AnnotationToPointData { - constructor() {} - - static convert(annotation, index, metadataProvider) { - validateAnnotation(annotation); - - const { toolName } = annotation.metadata; - const toolClass = AnnotationToPointData.TOOL_NAMES[toolName]; - - if (!toolClass) { - throw new Error( - `Unknown tool type: ${toolName}, cannot convert to RTSSReport` - ); - } - - // Each toolData should become a list of contours, ContourSequence - // contains a list of contours with their pointData, their geometry - // type and their length. - const ContourSequence = toolClass.getContourSequence( - annotation, - metadataProvider - ); - - // Todo: random rgb color for now, options should be passed in - const color = [ - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255), - Math.floor(Math.random() * 255), - ]; - - return { - ReferencedROINumber: index + 1, - ROIDisplayColor: color, - ContourSequence, - }; - } - - static register(toolClass) { - AnnotationToPointData.TOOL_NAMES[toolClass.toolName] = toolClass; - } -} - -AnnotationToPointData.TOOL_NAMES = {}; -AnnotationToPointData.register(RectangleROIStartEndThreshold); - -export default AnnotationToPointData; diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js deleted file mode 100644 index 803bef365d0..00000000000 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js +++ /dev/null @@ -1,56 +0,0 @@ -// comment -class RectangleROIStartEndThreshold { - constructor() {} - - static getContourSequence(toolData, metadataProvider) { - const { data } = toolData; - const { projectionPoints, projectionPointsImageIds } = data.cachedStats; - - return projectionPoints.map((point, index) => { - const ContourData = getPointData(point); - const ContourImageSequence = getContourImageSequence( - projectionPointsImageIds[index], - metadataProvider - ); - - return { - NumberOfContourPoints: ContourData.length / 3, - ContourImageSequence, - ContourGeometricType: 'CLOSED_PLANAR', - ContourData, - }; - }); - } -} - -RectangleROIStartEndThreshold.toolName = 'RectangleROIStartEndThreshold'; - -function getPointData(points) { - // Since this is a closed contour, the order of the points is important. - // re-order the points to be in the correct order clockwise - // Spread to make sure Float32Arrays are converted to arrays - const orderedPoints = [ - ...points[0], - ...points[1], - ...points[3], - ...points[2], - ]; - const pointsArray = orderedPoints.flat(); - - // reduce the precision of the points to 2 decimal places - const pointsArrayWithPrecision = pointsArray.map(point => { - return point.toFixed(2); - }); - - return pointsArrayWithPrecision; -} - -function getContourImageSequence(imageId, metadataProvider) { - const sopCommon = metadataProvider.get('sopCommonModule', imageId); - - return { - ReferencedSOPClassUID: sopCommon.sopClassUID, - ReferencedSOPInstanceUID: sopCommon.sopInstanceUID, - }; -} -export default RectangleROIStartEndThreshold; diff --git a/extensions/tmtv/src/utils/getThresholdValue.ts b/extensions/tmtv/src/utils/getThresholdValue.ts index d11872dd0f7..137b5b9a6de 100644 --- a/extensions/tmtv/src/utils/getThresholdValue.ts +++ b/extensions/tmtv/src/utils/getThresholdValue.ts @@ -3,10 +3,7 @@ import * as csTools from '@cornerstonejs/tools'; function getRoiStats(referencedVolume, annotations) { // roiStats const { imageData } = referencedVolume; - const values = imageData - .getPointData() - .getScalars() - .getData(); + const values = imageData.getPointData().getScalars().getData(); // Todo: add support for other strategies const { fn, baseValue } = _getStrategyFn('max'); @@ -59,9 +56,10 @@ function getThresholdValues( }; } -function _getStrategyFn( - statistic -): { fn: (a: number, b: number) => number; baseValue: number } { +function _getStrategyFn(statistic): { + fn: (a: number, b: number) => number; + baseValue: number; +} { const baseValue = -Infinity; const fn = (number, maxValue) => { if (number > maxValue) { diff --git a/extensions/tmtv/src/utils/hpViewports.ts b/extensions/tmtv/src/utils/hpViewports.ts index af2b760d436..b716a9ef102 100644 --- a/extensions/tmtv/src/utils/hpViewports.ts +++ b/extensions/tmtv/src/utils/hpViewports.ts @@ -114,6 +114,9 @@ const ptAXIAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -155,6 +158,9 @@ const ptSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -196,6 +202,9 @@ const ptCORONAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -246,6 +255,9 @@ const fusionAXIAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -254,16 +266,20 @@ const fusionAXIAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -302,6 +318,9 @@ const fusionSAGITTAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -310,16 +329,20 @@ const fusionSAGITTAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -358,6 +381,9 @@ const fusionCORONAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -366,16 +392,20 @@ const fusionCORONAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -399,6 +429,9 @@ const mipSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js index 5d2100aff8c..a6131e2ac5d 100644 --- a/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js +++ b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js @@ -10,11 +10,7 @@ const RectangleROIStartEndThreshold = { * @param {Object} cornerstone Cornerstone event data * @return {Measurement} Measurement instance */ - toMeasurement: ( - csToolsEventDetail, - displaySetService, - cornerstoneViewportService - ) => { + toMeasurement: (csToolsEventDetail, displaySetService, cornerstoneViewportService) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -30,11 +26,7 @@ const RectangleROIStartEndThreshold = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId diff --git a/jest.config.js b/jest.config.js index 22b0ac8f1e7..c19684dbde0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,5 +13,5 @@ module.exports = { '/extensions/*/jest.config.js', //'/modes/*/jest.config.js' // Enable if any mode definitions start including tests ], - coverageDirectory: "/coverage/" + coverageDirectory: '/coverage/', }; diff --git a/lerna.json b/lerna.json index 09786c5715b..23636c873b6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,5 @@ { - "version": "3.6.0", + "version": "3.7.0-beta.108", "packages": ["extensions/*", "platform/*", "modes/*"], - "npmClient": "yarn", - "useWorkspaces": true + "npmClient": "yarn" } diff --git a/modes/basic-dev-mode/.webpack/webpack.dev.js b/modes/basic-dev-mode/.webpack/webpack.dev.js index 2bc3ced0b9b..4bf848b6c5c 100644 --- a/modes/basic-dev-mode/.webpack/webpack.dev.js +++ b/modes/basic-dev-mode/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/basic-dev-mode/.webpack/webpack.prod.js b/modes/basic-dev-mode/.webpack/webpack.prod.js index 7147d3b06df..26c62c7d4ad 100644 --- a/modes/basic-dev-mode/.webpack/webpack.prod.js +++ b/modes/basic-dev-mode/.webpack/webpack.prod.js @@ -12,7 +12,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/basic-dev-mode/CHANGELOG.md b/modes/basic-dev-mode/CHANGELOG.md new file mode 100644 index 00000000000..e51a67a413a --- /dev/null +++ b/modes/basic-dev-mode/CHANGELOG.md @@ -0,0 +1,422 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode diff --git a/modes/basic-dev-mode/package.json b/modes/basic-dev-mode/package.json index 6c6b1504c22..1d63852ed6a 100644 --- a/modes/basic-dev-mode/package.json +++ b/modes/basic-dev-mode/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-basic-dev-mode", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Basic OHIF Viewer Using Cornerstone", "author": "OHIF", "license": "MIT", @@ -29,12 +29,12 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0" + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/extension-dicom-pdf": "3.7.0-beta.108", + "@ohif/extension-dicom-video": "3.7.0-beta.108" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/basic-dev-mode/src/index.js b/modes/basic-dev-mode/src/index.js index 0f769854ecd..411409e1f49 100644 --- a/modes/basic-dev-mode/src/index.js +++ b/modes/basic-dev-mode/src/index.js @@ -19,14 +19,12 @@ const cs3d = { }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -86,18 +84,18 @@ function modeFactory({ modeConfiguration }) { { toolName: toolNames.CalibrationLine }, ], // enabled + enabled: [{ toolName: toolNames.ImageOverlayViewer }], // disabled }; const toolGroupId = 'default'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, configs); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); let unsubscribe; const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -134,11 +132,7 @@ function modeFactory({ modeConfiguration }) { ]); }, onModeExit: ({ servicesManager }) => { - const { - toolGroupService, - measurementService, - toolbarService, - } = servicesManager.services; + const { toolGroupService, measurementService, toolbarService } = servicesManager.services; toolGroupService.destroy(); }, diff --git a/modes/basic-test-mode/.webpack/webpack.dev.js b/modes/basic-test-mode/.webpack/webpack.dev.js index 2bc3ced0b9b..1b8e34cfd13 100644 --- a/modes/basic-test-mode/.webpack/webpack.dev.js +++ b/modes/basic-test-mode/.webpack/webpack.dev.js @@ -4,10 +4,9 @@ const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); const ENTRY = { - app: `${SRC_DIR}/index.js`, + app: `${SRC_DIR}/index.ts`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/basic-test-mode/.webpack/webpack.prod.js b/modes/basic-test-mode/.webpack/webpack.prod.js index 677f4788132..0a7d7f8640e 100644 --- a/modes/basic-test-mode/.webpack/webpack.prod.js +++ b/modes/basic-test-mode/.webpack/webpack.prod.js @@ -11,7 +11,7 @@ const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); const ENTRY = { - app: `${SRC_DIR}/index.js`, + app: `${SRC_DIR}/index.ts`, }; module.exports = (env, argv) => { @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/basic-test-mode/CHANGELOG.md b/modes/basic-test-mode/CHANGELOG.md new file mode 100644 index 00000000000..9c2c9b2612d --- /dev/null +++ b/modes/basic-test-mode/CHANGELOG.md @@ -0,0 +1,426 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + + +### Features + +* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6)) + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-test diff --git a/modes/basic-test-mode/package.json b/modes/basic-test-mode/package.json index c832649cfdf..6712e85d11a 100644 --- a/modes/basic-test-mode/package.json +++ b/modes/basic-test-mode/package.json @@ -1,12 +1,12 @@ { "name": "@ohif/mode-test", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Basic mode for testing", "author": "OHIF", "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/ohif-mode-test.umd.js", - "module": "src/index.js", + "module": "src/index.ts", "engines": { "node": ">=14", "npm": ">=6", @@ -32,14 +32,14 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0", - "@ohif/extension-test": "3.6.0" + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/extension-dicom-pdf": "3.7.0-beta.108", + "@ohif/extension-dicom-video": "3.7.0-beta.108", + "@ohif/extension-measurement-tracking": "3.7.0-beta.108", + "@ohif/extension-test": "3.7.0-beta.108" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/basic-test-mode/src/index.js b/modes/basic-test-mode/src/index.ts similarity index 86% rename from modes/basic-test-mode/src/index.js rename to modes/basic-test-mode/src/index.ts index d465b7e0995..3d51b06f15a 100644 --- a/modes/basic-test-mode/src/index.js +++ b/modes/basic-test-mode/src/index.ts @@ -1,7 +1,7 @@ import { hotkeys } from '@ohif/core'; -import toolbarButtons from './toolbarButtons.js'; -import { id } from './id.js'; -import initToolGroups from './initToolGroups.js'; +import toolbarButtons from './toolbarButtons'; +import { id } from './id'; +import initToolGroups from './initToolGroups'; // Allow this mode by excluding non-imaging modalities such as SR, SEG // Also, SM is not a simple imaging modalities, so exclude it. @@ -14,22 +14,18 @@ const ohif = { }; const tracked = { - measurements: - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + measurements: '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList', - viewport: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + viewport: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -39,8 +35,7 @@ const dicompdf = { }; const dicomSeg = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', }; @@ -68,12 +63,8 @@ function modeFactory() { * Lifecycle hooks */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - measurementService, - toolbarService, - toolGroupService, - customizationService, - } = servicesManager.services; + const { measurementService, toolbarService, toolGroupService, customizationService } = + servicesManager.services; measurementService.clearMeasurements(); @@ -90,7 +81,6 @@ function modeFactory() { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -147,13 +137,12 @@ function modeFactory() { series: [], }, - isValidMode: function({ modalities }) { + isValidMode: function ({ modalities }) { const modalities_list = modalities.split('\\'); // Exclude non-image modalities - return !!modalities_list.filter( - modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 - ).length; + return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1) + .length; }, routes: [ { diff --git a/modes/basic-test-mode/src/initToolGroups.js b/modes/basic-test-mode/src/initToolGroups.ts similarity index 69% rename from modes/basic-test-mode/src/initToolGroups.js rename to modes/basic-test-mode/src/initToolGroups.ts index ccc5c1a6df2..3110f24f885 100644 --- a/modes/basic-test-mode/src/initToolGroups.js +++ b/modes/basic-test-mode/src/initToolGroups.ts @@ -1,9 +1,4 @@ -function initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - toolGroupId -) { +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -28,7 +23,23 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -44,24 +55,7 @@ function initDefaultToolGroup( disabled: [{ toolName: toolNames.ReferenceLines }], }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -122,25 +116,8 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -168,7 +145,23 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -179,7 +172,16 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.SegmentationDisplay }, ], disabled: [ - { toolName: toolNames.Crosshairs }, + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, { toolName: toolNames.ReferenceLines }, ], @@ -187,40 +189,11 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools('mpr', tools); } function initToolGroups(extensionManager, toolGroupService, commandsManager) { - initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - 'default' - ); + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initSRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); } diff --git a/modes/basic-test-mode/src/toolbarButtons.js b/modes/basic-test-mode/src/toolbarButtons.ts similarity index 90% rename from modes/basic-test-mode/src/toolbarButtons.js rename to modes/basic-test-mode/src/toolbarButtons.ts index 506e6b6529e..5d540b39c50 100644 --- a/modes/basic-test-mode/src/toolbarButtons.js +++ b/modes/basic-test-mode/src/toolbarButtons.ts @@ -5,43 +5,15 @@ import { // ListMenu, WindowLevelMenuItem, } from '@ohif/ui'; -import { defaults } from '@ohif/core'; +import { defaults, ToolbarService } from '@ohif/core'; +import type { Button, RunCommand } from '@ohif/core/types'; +import { EVENTS } from '@cornerstonejs/core'; const { windowLevelPresets } = defaults; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip, uiType) { - return { - id, - icon, - label, - type, - commands, - tooltip, - uiType, - }; -} -function _createCommands(commandName, toolName, toolGroupIds) { - return toolGroupIds.map(toolGroupId => ({ - /* It's a command that is being run when the button is clicked. */ - commandName, - commandOptions: { - toolName, - toolGroupId, - }, - context: 'CORNERSTONE', - })); -} - -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); +const _createActionButton = ToolbarService._createButton.bind(null, 'action'); +const _createToggleButton = ToolbarService._createButton.bind(null, 'toggle'); +const _createToolButton = ToolbarService._createButton.bind(null, 'tool'); /** * @@ -67,7 +39,21 @@ function _createWwwcPreset(preset, title, subtitle) { }; } -const toolbarButtons = [ +const ReferenceLinesCommands: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ReferenceLines', + }, + context: 'CORNERSTONE', + }, +]; + +const toolbarButtons: Button[] = [ // Measurement { id: 'MeasurementTools', @@ -513,26 +499,39 @@ const toolbarButtons = [ context: 'CORNERSTONE', }, ], - 'Flip Horizontal' + 'Flip Horizontally' ), - _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ + _createToggleButton( + 'StackImageSync', + 'link', + 'Stack Image Sync', + [ + { + commandName: 'toggleStackImageSync', + }, + ], + 'Enable position synchronization on stack viewports', { - commandName: 'toggleStackImageSync', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ]), + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: { + commandName: 'toggleStackImageSync', + commandOptions: { toggledState: true }, + }, + }, + } + ), _createToggleButton( 'ReferenceLines', 'tool-referenceLines', // change this with the new icon 'Reference Lines', - [ - { - commandName: 'toggleReferenceLines', - commandOptions: {}, - context: 'CORNERSTONE', + ReferenceLinesCommands, + 'Show Reference Lines', + { + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: ReferenceLinesCommands, + [EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesCommands, }, - ] + } ), _createToolButton( 'StackScroll', diff --git a/modes/longitudinal/.webpack/webpack.dev.js b/modes/longitudinal/.webpack/webpack.dev.js index 2bc3ced0b9b..4bf848b6c5c 100644 --- a/modes/longitudinal/.webpack/webpack.dev.js +++ b/modes/longitudinal/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/longitudinal/.webpack/webpack.prod.js b/modes/longitudinal/.webpack/webpack.prod.js index 98ab53e9248..6b84361f24b 100644 --- a/modes/longitudinal/.webpack/webpack.prod.js +++ b/modes/longitudinal/.webpack/webpack.prod.js @@ -39,13 +39,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/longitudinal/CHANGELOG.md b/modes/longitudinal/CHANGELOG.md new file mode 100644 index 00000000000..ae54fe7179d --- /dev/null +++ b/modes/longitudinal/CHANGELOG.md @@ -0,0 +1,428 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-longitudinal diff --git a/modes/longitudinal/package.json b/modes/longitudinal/package.json index ca4c55779b2..6746a8f8ae0 100644 --- a/modes/longitudinal/package.json +++ b/modes/longitudinal/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-longitudinal", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Longitudinal Workflow", "author": "OHIF", "license": "MIT", @@ -32,15 +32,15 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-rt": "3.6.0", - "@ohif/extension-cornerstone-dicom-seg": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0" + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/extension-dicom-pdf": "3.7.0-beta.108", + "@ohif/extension-dicom-video": "3.7.0-beta.108", + "@ohif/extension-measurement-tracking": "3.7.0-beta.108" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 0c3e3d4d420..25397a2cf74 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -1,7 +1,7 @@ import { hotkeys } from '@ohif/core'; -import toolbarButtons from './toolbarButtons.js'; -import { id } from './id.js'; -import initToolGroups from './initToolGroups.js'; +import toolbarButtons from './toolbarButtons'; +import { id } from './id'; +import initToolGroups from './initToolGroups'; // Allow this mode by excluding non-imaging modalities such as SR, SEG // Also, SM is not a simple imaging modalities, so exclude it. @@ -14,22 +14,18 @@ const ohif = { }; const tracked = { - measurements: - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + measurements: '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList', - viewport: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + viewport: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -39,16 +35,14 @@ const dicompdf = { }; const dicomSeg = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', }; -const dicomRt = { +const dicomRT = { viewport: '@ohif/extension-cornerstone-dicom-rt.viewportModule.dicom-rt', - sopClassHandler: - '@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt', + sopClassHandler: '@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt', }; const extensionDependencies = { @@ -63,7 +57,7 @@ const extensionDependencies = { '@ohif/extension-dicom-video': '^3.0.1', }; -function modeFactory() { +function modeFactory({ modeConfiguration }) { let _activatePanelTriggersSubscriptions = []; return { // TODO: We're using this as a route segment @@ -80,7 +74,7 @@ function modeFactory() { toolbarService, toolGroupService, panelService, - segmentationService, + customizationService, } = servicesManager.services; measurementService.clearMeasurements(); @@ -93,7 +87,6 @@ function modeFactory() { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -132,6 +125,13 @@ function modeFactory() { 'MoreTools', ]); + customizationService.addModeCustomizations([ + { + id: 'segmentation.disableEditing', + value: true, + }, + ]); + // // ActivatePanel event trigger for when a segmentation or measurement is added. // // Do not force activation so as to respect the state the user may have left the UI in. // _activatePanelTriggersSubscriptions = [ @@ -176,13 +176,12 @@ function modeFactory() { series: [], }, - isValidMode: function({ modalities }) { + isValidMode: function ({ modalities }) { const modalities_list = modalities.split('\\'); // Exclude non-image modalities - return !!modalities_list.filter( - modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 - ).length; + return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1) + .length; }, routes: [ { @@ -219,8 +218,8 @@ function modeFactory() { displaySetsToDisplay: [dicomSeg.sopClassHandler], }, { - namespace: dicomRt.viewport, - displaySetsToDisplay: [dicomRt.sopClassHandler], + namespace: dicomRT.viewport, + displaySetsToDisplay: [dicomRT.sopClassHandler], }, ], }, @@ -241,9 +240,10 @@ function modeFactory() { ohif.sopClassHandler, dicompdf.sopClassHandler, dicomsr.sopClassHandler, - dicomRt.sopClassHandler, + dicomRT.sopClassHandler, ], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index b8e88db909a..e7ea816db39 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -1,9 +1,4 @@ -function initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - toolGroupId -) { +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -28,7 +23,23 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -43,28 +54,12 @@ function initDefaultToolGroup( { toolName: toolNames.CalibrationLine }, ], // enabled + enabled: [{ toolName: toolNames.ImageOverlayViewer }], // disabled disabled: [{ toolName: toolNames.ReferenceLines }], }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -72,6 +67,10 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' ); + if (!SRUtilityModule) { + return; + } + const CS3DUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -125,25 +124,8 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -171,7 +153,23 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -184,7 +182,16 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.SegmentationDisplay }, ], disabled: [ - { toolName: toolNames.Crosshairs }, + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, { toolName: toolNames.ReferenceLines }, ], @@ -192,31 +199,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools('mpr', tools); } function initVolume3DToolGroup(extensionManager, toolGroupService) { const utilityModule = extensionManager.getModuleEntry( @@ -246,12 +229,7 @@ function initVolume3DToolGroup(extensionManager, toolGroupService) { } function initToolGroups(extensionManager, toolGroupService, commandsManager) { - initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - 'default' - ); + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initSRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); initVolume3DToolGroup(extensionManager, toolGroupService); diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.ts similarity index 88% rename from modes/longitudinal/src/toolbarButtons.js rename to modes/longitudinal/src/toolbarButtons.ts index 92de37e89dd..e7842c56521 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.ts @@ -5,31 +5,15 @@ import { // ListMenu, WindowLevelMenuItem, } from '@ohif/ui'; -import { defaults } from '@ohif/core'; +import { defaults, ToolbarService } from '@ohif/core'; +import type { Button, RunCommand } from '@ohif/core/types'; +import { EVENTS } from '@cornerstonejs/core'; const { windowLevelPresets } = defaults; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip, uiType) { - return { - id, - icon, - label, - type, - commands, - tooltip, - uiType, - }; -} -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); +const _createActionButton = ToolbarService._createButton.bind(null, 'action'); +const _createToggleButton = ToolbarService._createButton.bind(null, 'toggle'); +const _createToolButton = ToolbarService._createButton.bind(null, 'tool'); /** * @@ -75,7 +59,21 @@ function _createSetToolActiveCommands(toolName) { return temp; } -const toolbarButtons = [ +const ReferenceLinesCommands: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ReferenceLines', + }, + context: 'CORNERSTONE', + }, +]; + +const toolbarButtons: Button[] = [ // Measurement { id: 'MeasurementTools', @@ -421,24 +419,53 @@ const toolbarButtons = [ ], 'Flip Horizontal' ), - _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ + _createToggleButton( + 'StackImageSync', + 'link', + 'Stack Image Sync', + [ + { + commandName: 'toggleStackImageSync', + }, + ], + 'Enable position synchronization on stack viewports', { - commandName: 'toggleStackImageSync', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ]), + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: { + commandName: 'toggleStackImageSync', + commandOptions: { toggledState: true }, + }, + }, + } + ), _createToggleButton( 'ReferenceLines', 'tool-referenceLines', // change this with the new icon 'Reference Lines', + ReferenceLinesCommands, + 'Show Reference Lines', + { + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: ReferenceLinesCommands, + [EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesCommands, + }, + } + ), + _createToggleButton( + 'ImageOverlayViewer', + 'toggle-dicom-overlay', + 'Image Overlay', [ { - commandName: 'toggleReferenceLines', - commandOptions: {}, + commandName: 'setToolActive', + commandOptions: { + toolName: 'ImageOverlayViewer', + }, context: 'CORNERSTONE', }, - ] + ], + 'Image Overlay', + { isActive: true } ), _createToolButton( 'StackScroll', diff --git a/modes/microscopy/.prettierrc b/modes/microscopy/.prettierrc index b80ec6b3474..ef83baaef93 100644 --- a/modes/microscopy/.prettierrc +++ b/modes/microscopy/.prettierrc @@ -1,8 +1,11 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid", + "endOfLine": "auto" } diff --git a/modes/microscopy/.webpack/webpack.dev.js b/modes/microscopy/.webpack/webpack.dev.js index cf62de65167..4bf848b6c5c 100644 --- a/modes/microscopy/.webpack/webpack.dev.js +++ b/modes/microscopy/.webpack/webpack.dev.js @@ -3,13 +3,10 @@ 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.js`, }; - - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/microscopy/.webpack/webpack.prod.js b/modes/microscopy/.webpack/webpack.prod.js index 6379fa8e534..f5781229458 100644 --- a/modes/microscopy/.webpack/webpack.prod.js +++ b/modes/microscopy/.webpack/webpack.prod.js @@ -39,13 +39,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/microscopy/CHANGELOG.md b/modes/microscopy/CHANGELOG.md new file mode 100644 index 00000000000..64be12c1673 --- /dev/null +++ b/modes/microscopy/CHANGELOG.md @@ -0,0 +1,419 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-microscopy diff --git a/modes/microscopy/babel.config.js b/modes/microscopy/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/modes/microscopy/babel.config.js +++ b/modes/microscopy/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json index 654f0b88be5..34b0a7b4827 100644 --- a/modes/microscopy/package.json +++ b/modes/microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-microscopy", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "OHIF mode for DICOM microscopy", "author": "OHIF", "license": "MIT", @@ -33,8 +33,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-dicom-microscopy": "3.6.0" + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.108" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx index 9044ad6eb33..5f170ca265d 100644 --- a/modes/microscopy/src/index.tsx +++ b/modes/microscopy/src/index.tsx @@ -16,8 +16,7 @@ export const cornerstone = { }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -36,7 +35,7 @@ const extensionDependencies = { '@ohif/extension-dicom-microscopy': '^3.0.0', }; -function modeFactory() { +function modeFactory({ modeConfiguration }) { return { // TODO: We're using this as a route segment // We should not be. @@ -52,10 +51,7 @@ function modeFactory() { toolbarService.init(extensionManager); toolbarService.addButtons(toolbarButtons); - toolbarService.createButtonSection('primary', [ - 'MeasurementTools', - 'dragPan', - ]); + toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan']); }, onModeExit: ({ servicesManager }) => { @@ -89,13 +85,10 @@ function modeFactory() { leftPanels: [ohif.leftPanel], leftPanelDefaultClosed: true, // we have problem with rendering thumbnails for microscopy images rightPanelDefaultClosed: true, // we do not have the save microscopy measurements yet - rightPanels: [ - '@ohif/extension-dicom-microscopy.panelModule.measure', - ], + rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'], viewports: [ { - namespace: - '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom', + namespace: '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom', displaySetsToDisplay: [ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler', '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler', @@ -130,6 +123,7 @@ function modeFactory() { dicompdf.sopClassHandler, ], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/segmentation/.gitignore b/modes/segmentation/.gitignore new file mode 100644 index 00000000000..67045665db2 --- /dev/null +++ b/modes/segmentation/.gitignore @@ -0,0 +1,104 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/modes/segmentation/.prettierrc b/modes/segmentation/.prettierrc new file mode 100644 index 00000000000..28448abdf42 --- /dev/null +++ b/modes/segmentation/.prettierrc @@ -0,0 +1,11 @@ +{ + "trailingComma": "es5", + "printWidth": 100, + "proseWrap": "always", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "arrowParens": "avoid", + "singleAttributePerLine": true, + "endOfLine": "auto" +} diff --git a/modes/segmentation/.webpack/webpack.prod.js b/modes/segmentation/.webpack/webpack.prod.js new file mode 100644 index 00000000000..163392a699a --- /dev/null +++ b/modes/segmentation/.webpack/webpack.prod.js @@ -0,0 +1,62 @@ +const path = require('path'); +const pkg = require('../package.json'); + +const outputFile = 'index.umd.js'; +const rootDir = path.resolve(__dirname, '../'); +const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`); + +// Todo: add ESM build for the mode in addition to umd build +const config = { + mode: 'production', + entry: rootDir + '/' + pkg.module, + devtool: 'source-map', + output: { + path: outputFolder, + filename: outputFile, + library: pkg.name, + libraryTarget: 'umd', + chunkFilename: '[name].chunk.js', + umdNamedDefine: true, + globalObject: "typeof self !== 'undefined' ? self : this", + }, + externals: [ + { + react: { + root: 'React', + commonjs2: 'react', + commonjs: 'react', + amd: 'react', + }, + '@ohif/core': { + commonjs2: '@ohif/core', + commonjs: '@ohif/core', + amd: '@ohif/core', + root: '@ohif/core', + }, + '@ohif/ui': { + commonjs2: '@ohif/ui', + commonjs: '@ohif/ui', + amd: '@ohif/ui', + root: '@ohif/ui', + }, + }, + ], + module: { + rules: [ + { + test: /(\.jsx|\.js|\.tsx|\.ts)$/, + loader: 'babel-loader', + exclude: /(node_modules|bower_components)/, + resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + ], + }, + resolve: { + modules: [path.resolve('./node_modules'), path.resolve('./src')], + extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'], + }, +}; + +module.exports = config; diff --git a/modes/segmentation/CHANGELOG.md b/modes/segmentation/CHANGELOG.md new file mode 100644 index 00000000000..ace09f157a6 --- /dev/null +++ b/modes/segmentation/CHANGELOG.md @@ -0,0 +1,238 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) diff --git a/modes/segmentation/LICENSE b/modes/segmentation/LICENSE new file mode 100644 index 00000000000..c58f05915f0 --- /dev/null +++ b/modes/segmentation/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 @ohif-segmentation-mode (contact@ohif.org) + +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. \ No newline at end of file diff --git a/modes/segmentation/README.md b/modes/segmentation/README.md new file mode 100644 index 00000000000..5bf905d9270 --- /dev/null +++ b/modes/segmentation/README.md @@ -0,0 +1,7 @@ +# @ohif-segmentation-mode +## Description +OHIF segmentation mode which enables labelmap segmentation read/edit/export +## Author +@ohif +## License +MIT \ No newline at end of file diff --git a/modes/segmentation/babel.config.js b/modes/segmentation/babel.config.js new file mode 100644 index 00000000000..a38ddda2127 --- /dev/null +++ b/modes/segmentation/babel.config.js @@ -0,0 +1,44 @@ +module.exports = { + plugins: ['inline-react-svg', '@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__'], + }, + }, +}; diff --git a/modes/segmentation/package.json b/modes/segmentation/package.json new file mode 100644 index 00000000000..f3940522f94 --- /dev/null +++ b/modes/segmentation/package.json @@ -0,0 +1,67 @@ +{ + "name": "@ohif/mode-segmentation", + "version": "3.7.0-beta.108", + "description": "OHIF segmentation mode which enables labelmap segmentation read/edit/export", + "author": "@ohif", + "license": "MIT", + "main": "dist/umd/@ohif/mode-segmentation/index.umd.js", + "files": [ + "dist/**", + "public/**", + "README.md" + ], + "repository": "OHIF/Viewers", + "keywords": [ + "ohif-mode" + ], + "publishConfig": { + "access": "public" + }, + "module": "src/index.tsx", + "engines": { + "node": ">=14", + "npm": ">=7", + "yarn": ">=1.16.0" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "3.7.0-beta.108" + }, + "dependencies": { + "@babel/runtime": "^7.20.13" + }, + "devDependencies": { + "@babel/core": "^7.21.4", + "@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-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.17.0", + "@babel/plugin-transform-typescript": "^7.13.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.13.0", + "babel-eslint": "^8.0.3", + "babel-loader": "^8.0.0-beta.4", + "babel-plugin-inline-react-svg": "^2.0.1", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^10.2.0", + "cross-env": "^7.0.3", + "dotenv": "^14.1.0", + "eslint": "^5.0.1", + "eslint-loader": "^2.0.0", + "webpack": "^5.50.0", + "webpack-cli": "^4.7.2", + "webpack-merge": "^5.7.3" + } +} diff --git a/modes/segmentation/src/id.js b/modes/segmentation/src/id.js new file mode 100644 index 00000000000..ebe5acd98ae --- /dev/null +++ b/modes/segmentation/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/modes/segmentation/src/index.tsx b/modes/segmentation/src/index.tsx new file mode 100644 index 00000000000..a6f552c5aee --- /dev/null +++ b/modes/segmentation/src/index.tsx @@ -0,0 +1,179 @@ +import { hotkeys } from '@ohif/core'; +import { id } from './id'; +import toolbarButtons from './toolbarButtons'; +import initToolGroups from './initToolGroups'; + +const ohif = { + layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', + hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default', + leftPanel: '@ohif/extension-default.panelModule.seriesList', + rightPanel: '@ohif/extension-default.panelModule.measure', +}; + +const cornerstone = { + viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', +}; + +const segmentation = { + panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', + panelTool: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentationWithTools', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', +}; + +/** + * Just two dependencies to be able to render a viewport with panels in order + * to make sure that the mode is working. + */ +const extensionDependencies = { + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-cornerstone-dicom-seg': '^3.0.0', +}; + +function modeFactory({ modeConfiguration }) { + return { + /** + * Mode ID, which should be unique among modes used by the viewer. This ID + * is used to identify the mode in the viewer's state. + */ + id, + routeName: 'segmentation', + /** + * Mode name, which is displayed in the viewer's UI in the workList, for the + * user to select the mode. + */ + displayName: 'Segmentation', + /** + * Runs when the Mode Route is mounted to the DOM. Usually used to initialize + * Services and other resources. + */ + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + const { measurementService, toolbarService, toolGroupService } = servicesManager.services; + + measurementService.clearMeasurements(); + + // Init Default and SR ToolGroups + initToolGroups(extensionManager, toolGroupService, commandsManager); + + let unsubscribe; + + const activateTool = () => { + toolbarService.recordInteraction({ + groupId: 'WindowLevel', + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + + // We don't need to reset the active tool whenever a viewport is getting + // added to the toolGroup. + unsubscribe(); + }; + + // Since we only have one viewport for the basic cs3d mode and it has + // only one hanging protocol, we can just use the first viewport + ({ unsubscribe } = toolGroupService.subscribe( + toolGroupService.EVENTS.VIEWPORT_ADDED, + activateTool + )); + + toolbarService.init(extensionManager); + toolbarService.addButtons(toolbarButtons); + toolbarService.createButtonSection('primary', [ + 'Zoom', + 'WindowLevel', + 'Pan', + 'Capture', + 'Layout', + 'MPR', + 'Crosshairs', + 'MoreTools', + ]); + }, + onModeExit: ({ servicesManager }) => { + const { + toolGroupService, + syncGroupService, + toolbarService, + segmentationService, + cornerstoneViewportService, + } = servicesManager.services; + + toolGroupService.destroy(); + syncGroupService.destroy(); + segmentationService.destroy(); + cornerstoneViewportService.destroy(); + }, + /** */ + validationTags: { + study: [], + series: [], + }, + /** + * A boolean return value that indicates whether the mode is valid for the + * modalities of the selected studies. For instance a PET/CT mode should be + */ + isValidMode: ({ modalities }) => true, + /** + * Mode Routes are used to define the mode's behavior. A list of Mode Route + * that includes the mode's path and the layout to be used. The layout will + * include the components that are used in the layout. For instance, if the + * default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout') + * it will include the leftPanels, rightPanels, and viewports. However, if + * you define another layoutTemplate that includes a Footer for instance, + * you should provide the Footer component here too. Note: We use Strings + * to reference the component's ID as they are registered in the internal + * ExtensionManager. The template for the string is: + * `${extensionId}.{moduleType}.${componentId}`. + */ + routes: [ + { + path: 'template', + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + leftPanels: [ohif.leftPanel], + rightPanels: [segmentation.panelTool], + viewports: [ + { + namespace: cornerstone.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + { + namespace: segmentation.viewport, + displaySetsToDisplay: [segmentation.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + /** List of extensions that are used by the mode */ + extensions: extensionDependencies, + /** HangingProtocol used by the mode */ + // hangingProtocol: [''], + /** SopClassHandlers used by the mode */ + sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler], + /** hotkeys for mode */ + hotkeys: [...hotkeys.defaults.hotkeyBindings], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; diff --git a/modes/segmentation/src/initToolGroups.ts b/modes/segmentation/src/initToolGroups.ts new file mode 100644 index 00000000000..58204839ee4 --- /dev/null +++ b/modes/segmentation/src/initToolGroups.ts @@ -0,0 +1,82 @@ +const brushInstanceNames = { + CircularBrush: 'CircularBrush', + CircularEraser: 'CircularEraser', + SphereBrush: 'SphereBrush', + SphereEraser: 'SphereEraser', + ThresholdCircularBrush: 'ThresholdCircularBrush', + ThresholdSphereBrush: 'ThresholdSphereBrush', +}; + +const brushStrategies = { + CircularBrush: 'FILL_INSIDE_CIRCLE', + CircularEraser: 'ERASE_INSIDE_CIRCLE', + SphereBrush: 'FILL_INSIDE_SPHERE', + SphereEraser: 'ERASE_INSIDE_SPHERE', + ThresholdCircularBrush: 'THRESHOLD_INSIDE_CIRCLE', + ThresholdSphereBrush: 'THRESHOLD_INSIDE_SPHERE', +}; + +function createTools(utilityModule) { + const { toolNames, Enums } = utilityModule.exports; + return { + active: [ + { toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] }, + { toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] }, + { toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }] }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: Object.keys(brushInstanceNames) + .map(brushName => ({ + toolName: brushName, + parentTool: 'Brush', + configuration: { + activeStrategy: brushStrategies[brushName], + }, + })) + .concat([ + { toolName: toolNames.CircleScissors }, + { toolName: toolNames.RectangleScissors }, + { toolName: toolNames.SphereScissors }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Magnify }, + { toolName: toolNames.SegmentationDisplay }, + ]), + disabled: [{ toolName: toolNames.ReferenceLines }], + }; +} + +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); +} + +function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); + tools.disabled.push( + { + toolName: utilityModule.exports.toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, + { toolName: utilityModule.exports.toolNames.ReferenceLines } + ); + toolGroupService.createToolGroupAndAddTools('mpr', tools); +} + +function initToolGroups(extensionManager, toolGroupService, commandsManager) { + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); + initMPRToolGroup(extensionManager, toolGroupService, commandsManager); +} + +export default initToolGroups; diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts new file mode 100644 index 00000000000..b17a33deae1 --- /dev/null +++ b/modes/segmentation/src/toolbarButtons.ts @@ -0,0 +1,356 @@ +import { + // ExpandableToolbarButton, + // ListMenu, + WindowLevelMenuItem, +} from '@ohif/ui'; +import { defaults } from '@ohif/core'; + +const { windowLevelPresets } = defaults; +/** + * + * @param {*} type - 'tool' | 'action' | 'toggle' + * @param {*} id + * @param {*} icon + * @param {*} label + */ +function _createButton(type, id, icon, label, commands, tooltip, uiType) { + return { + id, + icon, + label, + type, + commands, + tooltip, + uiType, + }; +} + +const _createActionButton = _createButton.bind(null, 'action'); +const _createToggleButton = _createButton.bind(null, 'toggle'); +const _createToolButton = _createButton.bind(null, 'tool'); + +/** + * + * @param {*} preset - preset number (from above import) + * @param {*} title + * @param {*} subtitle + */ +function _createWwwcPreset(preset, title, subtitle) { + return { + id: preset.toString(), + title, + subtitle, + type: 'action', + commands: [ + { + commandName: 'setWindowLevel', + commandOptions: { + ...windowLevelPresets[preset], + }, + context: 'CORNERSTONE', + }, + ], + }; +} + +const toolGroupIds = ['default', 'mpr', 'SRToolGroup']; + +/** + * Creates an array of 'setToolActive' commands for the given toolName - one for + * each toolGroupId specified in toolGroupIds. + * @param {string} toolName + * @returns {Array} an array of 'setToolActive' commands + */ +function _createSetToolActiveCommands(toolName) { + const temp = toolGroupIds.map(toolGroupId => ({ + commandName: 'setToolActive', + commandOptions: { + toolGroupId, + toolName, + }, + context: 'CORNERSTONE', + })); + return temp; +} + +const toolbarButtons = [ + // Zoom.. + { + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commands: _createSetToolActiveCommands('Zoom'), + }, + }, + // Window Level + Presets... + { + id: 'WindowLevel', + type: 'ohif.splitButton', + props: { + groupId: 'WindowLevel', + primary: _createToolButton( + 'WindowLevel', + 'tool-window-level', + 'Window Level', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + 'Window Level' + ), + secondary: { + icon: 'chevron-down', + label: 'W/L Manual', + isActive: true, + tooltip: 'W/L Presets', + }, + isAction: true, // ? + renderer: WindowLevelMenuItem, + items: [ + _createWwwcPreset(1, 'Soft tissue', '400 / 40'), + _createWwwcPreset(2, 'Lung', '1500 / -600'), + _createWwwcPreset(3, 'Liver', '150 / 90'), + _createWwwcPreset(4, 'Bone', '2500 / 480'), + _createWwwcPreset(5, 'Brain', '80 / 40'), + ], + }, + }, + // Pan... + { + id: 'Pan', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-move', + label: 'Pan', + commands: _createSetToolActiveCommands('Pan'), + }, + }, + { + id: 'Capture', + type: 'ohif.action', + props: { + icon: 'tool-capture', + label: 'Capture', + type: 'action', + commands: [ + { + commandName: 'showDownloadViewportModal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Layout', + type: 'ohif.layoutSelector', + props: { + rows: 3, + columns: 3, + }, + }, + { + id: 'MPR', + type: 'ohif.action', + props: { + type: 'toggle', + icon: 'icon-mpr', + label: 'MPR', + commands: [ + { + commandName: 'toggleHangingProtocol', + commandOptions: { + protocolId: 'mpr', + }, + context: 'DEFAULT', + }, + ], + }, + }, + { + id: 'Crosshairs', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Crosshairs', + toolGroupId: 'mpr', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + // More... + { + id: 'MoreTools', + type: 'ohif.splitButton', + props: { + isRadio: true, // ? + groupId: 'MoreTools', + primary: _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Tools', + }, + items: [ + _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + _createActionButton( + 'rotate-right', + 'tool-rotate-right', + 'Rotate Right', + [ + { + commandName: 'rotateViewportCW', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Rotate +90' + ), + _createActionButton( + 'flip-horizontal', + 'tool-flip-horizontal', + 'Flip Horizontally', + [ + { + commandName: 'flipViewportHorizontal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Flip Horizontal' + ), + _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ + { + commandName: 'toggleStackImageSync', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ]), + _createToggleButton( + 'ReferenceLines', + 'tool-referenceLines', // change this with the new icon + 'Reference Lines', + [ + { + commandName: 'toggleReferenceLines', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ] + ), + _createToolButton( + 'StackScroll', + 'tool-stack-scroll', + 'Stack Scroll', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'StackScroll', + }, + context: 'CORNERSTONE', + }, + ], + 'Stack Scroll' + ), + _createActionButton( + 'invert', + 'tool-invert', + 'Invert', + [ + { + commandName: 'invertViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Invert Colors' + ), + _createToggleButton( + 'cine', + 'tool-cine', + 'Cine', + [ + { + commandName: 'toggleCine', + context: 'CORNERSTONE', + }, + ], + 'Cine' + ), + _createToolButton( + 'Magnify', + 'tool-magnify', + 'Magnify', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Magnify', + }, + context: 'CORNERSTONE', + }, + ], + 'Magnify' + ), + _createActionButton( + 'TagBrowser', + 'list-bullets', + 'Dicom Tag Browser', + [ + { + commandName: 'openDICOMTagViewer', + commandOptions: {}, + context: 'DEFAULT', + }, + ], + 'Dicom Tag Browser' + ), + ], + }, + }, +]; + +export default toolbarButtons; diff --git a/modes/tmtv/.webpack/webpack.dev.js b/modes/tmtv/.webpack/webpack.dev.js index 2bc3ced0b9b..4bf848b6c5c 100644 --- a/modes/tmtv/.webpack/webpack.dev.js +++ b/modes/tmtv/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/tmtv/.webpack/webpack.prod.js b/modes/tmtv/.webpack/webpack.prod.js index 385ed85ced1..018db058d32 100644 --- a/modes/tmtv/.webpack/webpack.prod.js +++ b/modes/tmtv/.webpack/webpack.prod.js @@ -14,7 +14,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); @@ -41,13 +40,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/tmtv/CHANGELOG.md b/modes/tmtv/CHANGELOG.md new file mode 100644 index 00000000000..48af4ed914c --- /dev/null +++ b/modes/tmtv/CHANGELOG.md @@ -0,0 +1,422 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-tmtv diff --git a/modes/tmtv/package.json b/modes/tmtv/package.json index dcd6936744a..1a6ff7f4349 100644 --- a/modes/tmtv/package.json +++ b/modes/tmtv/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-tmtv", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Total Metabolic Tumor Volume Workflow", "author": "OHIF", "license": "MIT", @@ -32,13 +32,13 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0" + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/extension-dicom-pdf": "3.7.0-beta.108", + "@ohif/extension-dicom-video": "3.7.0-beta.108", + "@ohif/extension-measurement-tracking": "3.7.0-beta.108" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/tmtv/src/index.js b/modes/tmtv/src/index.js index aae993bbdc5..110fb0fb753 100644 --- a/modes/tmtv/src/index.js +++ b/modes/tmtv/src/index.js @@ -43,12 +43,8 @@ function modeFactory({ modeConfiguration }) { * Lifecycle hooks */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - toolbarService, - toolGroupService, - hangingProtocolService, - displaySetService, - } = servicesManager.services; + const { toolbarService, toolGroupService, hangingProtocolService, displaySetService } = + servicesManager.services; const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' @@ -62,7 +58,6 @@ function modeFactory({ modeConfiguration }) { const setWindowLevelActive = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -99,9 +94,7 @@ function modeFactory({ modeConfiguration }) { // For fusion toolGroup we need to add the volumeIds for the crosshairs // since in the fusion viewport we don't want both PT and CT to render MIP // when slabThickness is modified - const { - displaySetMatchDetails, - } = hangingProtocolService.getMatchDetails(); + const { displaySetMatchDetails } = hangingProtocolService.getMatchDetails(); setCrosshairsConfiguration( displaySetMatchDetails, @@ -143,22 +136,16 @@ function modeFactory({ modeConfiguration }) { 'getPTVOIRange', 'get PT VOI based on corrected or not', props => { - const ptDisplaySet = props.find( - imageSet => imageSet.Modality === 'PT' - ); + const ptDisplaySet = props.find(imageSet => imageSet.Modality === 'PT'); if (!ptDisplaySet) { return; } const { imageId } = ptDisplaySet.images[0]; - const imageIdScalingFactor = MetadataProvider.get( - 'scalingModule', - imageId - ); + const imageIdScalingFactor = MetadataProvider.get('scalingModule', imageId); - const isSUVAvailable = - imageIdScalingFactor && imageIdScalingFactor.suvbw; + const isSUVAvailable = imageIdScalingFactor && imageIdScalingFactor.suvbw; if (isSUVAvailable) { return { @@ -196,16 +183,13 @@ function modeFactory({ modeConfiguration }) { const isValid = modalities_list.includes('CT') && modalities_list.includes('PT') && - !invalidModalities.some(modality => - modalities_list.includes(modality) - ) && + !invalidModalities.some(modality => modalities_list.includes(modality)) && // This is study is a 4D study with PT and CT and not a 3D study for the tmtv // mode, until we have a better way to identify 4D studies we will use the // StudyInstanceUID to identify the study // Todo: when we add the 4D mode which comes with a mechanism to identify // 4D studies we can use that - study.studyInstanceUid !== - '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849'; + study.studyInstanceUid !== '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849'; // there should be both CT and PT modalities and the modality should not be SM return isValid; @@ -220,7 +204,8 @@ function modeFactory({ modeConfiguration }) { return { id: ohif.layout, props: { - // leftPanels: [ohif.thumbnailList], + leftPanels: [ohif.thumbnailList], + leftPanelDefaultClosed: true, rightPanels: [tmtv.ROIThresholdPanel, tmtv.petSUV], viewports: [ { @@ -237,6 +222,7 @@ function modeFactory({ modeConfiguration }) { hangingProtocol: tmtv.hangingProtocol, sopClassHandlers: [ohif.sopClassHandler], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/tmtv/src/initToolGroups.js b/modes/tmtv/src/initToolGroups.js index 990b70058ae..a8f4fcf31b7 100644 --- a/modes/tmtv/src/initToolGroups.js +++ b/modes/tmtv/src/initToolGroups.js @@ -26,7 +26,24 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + }, + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.Probe }, @@ -38,157 +55,54 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { { toolName: toolNames.Magnify }, ], enabled: [{ toolName: toolNames.SegmentationDisplay }], - disabled: [{ toolName: toolNames.Crosshairs }], - }; - - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => { - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }); + disabled: [ + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, }, - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, + ], }; - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.CT, - tools, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.PT, - { - active: tools.active, - passive: [ - ...tools.passive, - { toolName: 'RectangleROIStartEndThreshold' }, - ], - enabled: tools.enabled, - disabled: tools.disabled, - }, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.Fusion, - tools, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.default, - tools, - toolsConfig - ); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.CT, tools); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.PT, { + active: tools.active, + passive: [...tools.passive, { toolName: 'RectangleROIStartEndThreshold' }], + enabled: tools.enabled, + disabled: tools.disabled, + }); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.Fusion, tools); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.default, tools); const mipTools = { active: [ { toolName: toolNames.VolumeRotateMouseWheel, + configuration: { + rotateIncrementDegrees: 0.1, + }, }, { toolName: toolNames.MipJumpToClick, + configuration: { + toolGroupId: toolGroupIds.PT, + }, bindings: [{ mouseButton: Enums.MouseBindings.Primary }], }, ], enabled: [{ toolName: toolNames.SegmentationDisplay }], }; - const mipToolsConfig = { - [toolNames.VolumeRotateMouseWheel]: { - rotateIncrementDegrees: 0.1, - }, - [toolNames.MipJumpToClick]: { - targetViewportIds: ['ptAXIAL', 'ptCORONAL', 'ptSAGITTAL'], - }, - }; - - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.MIP, - mipTools, - mipToolsConfig - ); -} - -function initMPRToolGroup(toolNames, Enums, toolGroupService, commandsManager) { - const tools = { - active: [ - { - toolName: toolNames.WindowLevel, - bindings: [{ mouseButton: Enums.MouseBindings.Primary }], - }, - { - toolName: toolNames.Pan, - bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], - }, - { - toolName: toolNames.Zoom, - bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], - }, - { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, - ], - passive: [ - { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, - { toolName: toolNames.Bidirectional }, - { toolName: toolNames.DragProbe }, - { toolName: toolNames.EllipticalROI }, - { toolName: toolNames.RectangleROI }, - { toolName: toolNames.StackScroll }, - { toolName: toolNames.Angle }, - { toolName: toolNames.CobbAngle }, - { toolName: toolNames.SegmentationDisplay }, - ], - disabled: [{ toolName: toolNames.Crosshairs }], - - // enabled - // disabled - }; - - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.MIP, mipTools); } function initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { _initToolGroups(toolNames, Enums, toolGroupService, commandsManager); - // initMPRToolGroup(toolNames, Enums, toolGroupService, commandsManager); } export default initToolGroups; diff --git a/modes/tmtv/src/toolbarButtons.js b/modes/tmtv/src/toolbarButtons.js index f6f70722138..556e2172845 100644 --- a/modes/tmtv/src/toolbarButtons.js +++ b/modes/tmtv/src/toolbarButtons.js @@ -24,9 +24,8 @@ function _createButton(type, id, icon, label, commands, tooltip) { function _createColormap(label, colormap) { return { - id: label.toString(), - title: label, - subtitle: label, + id: label, + label, type: 'action', commands: [ { @@ -36,13 +35,6 @@ function _createColormap(label, colormap) { colormap, }, }, - { - commandName: 'setFusionPTColormap', - commandOptions: { - toolGroupId: toolGroupIds.Fusion, - colormap, - }, - }, ], }; } @@ -173,20 +165,6 @@ const toolbarButtons = [ ], 'Ellipse Tool' ), - _createToolButton( - 'CircleROI', - 'tool-circle', - 'Circle', - [ - ..._createCommands('setToolActive', 'CircleROI', [ - toolGroupIds.CT, - toolGroupIds.PT, - toolGroupIds.Fusion, - // toolGroupIds.MPR, - ]), - ], - 'Circle Tool' - ), ], }, }, @@ -306,9 +284,7 @@ const toolbarButtons = [ icon: 'tool-create-threshold', label: 'Rectangle ROI Threshold', commands: [ - ..._createCommands('setToolActive', 'RectangleROIStartEndThreshold', [ - toolGroupIds.PT, - ]), + ..._createCommands('setToolActive', 'RectangleROIStartEndThreshold', [toolGroupIds.PT]), { commandName: 'displayNotification', commandOptions: { @@ -345,7 +321,6 @@ const toolbarButtons = [ tooltip: 'PET Image Colormap', }, isAction: true, // ? - renderer: WindowLevelMenuItem, items: [ _createColormap('HSV', 'hsv'), _createColormap('Hot Iron', 'hot_iron'), diff --git a/modes/tmtv/src/utils/setCrosshairsConfiguration.js b/modes/tmtv/src/utils/setCrosshairsConfiguration.js index cc6b4f57daa..072d9443e52 100644 --- a/modes/tmtv/src/utils/setCrosshairsConfiguration.js +++ b/modes/tmtv/src/utils/setCrosshairsConfiguration.js @@ -13,9 +13,7 @@ export default function setCrosshairsConfiguration( } const { SeriesInstanceUID } = matchDetails; - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); const toolConfig = toolGroupService.getToolConfiguration( toolGroupIds.Fusion, diff --git a/modes/tmtv/src/utils/setFusionActiveVolume.js b/modes/tmtv/src/utils/setFusionActiveVolume.js index ed7b1f6a98d..30d42455c78 100644 --- a/modes/tmtv/src/utils/setFusionActiveVolume.js +++ b/modes/tmtv/src/utils/setFusionActiveVolume.js @@ -14,9 +14,7 @@ export default function setFusionActiveVolume( const { SeriesInstanceUID } = matchDetails; - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); if (!displaySets || displaySets.length === 0) { return; diff --git a/package.json b/package.json index 3d663c75a34..cb9d4855489 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ] }, "engines": { - "node": ">=14", + "node": ">=16", "npm": ">=6", "yarn": ">=1.16.0" }, @@ -28,11 +28,12 @@ "build:package-all": "lerna run build:package --parallel --stream", "build:package-all-1": "lerna run build:package-1 --parallel --stream", "dev": "lerna run dev:viewer --stream", + "dev:no:cache": "lerna run dev:no:cache --stream", "dev:project": ".scripts/dev.sh", "dev:orthanc": "lerna run dev:orthanc --stream", "dev:dcm4chee": "lerna run dev:dcm4chee --stream", "dev:static": "lerna run dev:static --stream", - "orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up", + "orthanc:up": "docker-compose -f platform/app/.recipes/Nginx-Orthanc/docker-compose.yml up", "preinstall": "node preinstall.js", "start": "yarn run dev", "test": "yarn run test:unit", @@ -68,6 +69,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", @@ -77,8 +79,8 @@ "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", "@types/jest": "^27.5.0", - "@typescript-eslint/eslint-plugin": "^4.19.0", - "@typescript-eslint/parser": "^4.19.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", @@ -87,31 +89,31 @@ "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^9.0.1", "cross-env": "^5.2.0", - "css-loader": "^3.2.0", + "css-loader": "^6.8.1", "dotenv": "^8.1.0", "eslint": "^7.32.0", "eslint-config-prettier": "^7.2.0", "eslint-config-react-app": "^6.0.0", - "eslint-plugin-flowtype": "^7.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": "^4.0.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": "^7.1.1", + "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-environment-jsdom": "^29.5.0", "jest-canvas-mock": "^2.1.0", + "jest-environment-jsdom": "^29.5.0", "jest-junit": "^6.4.0", - "lerna": "^6.6.1", + "lerna": "^7.2.0", "lint-staged": "^9.0.2", "mini-css-extract-plugin": "^2.1.0", "optimize-css-assets-webpack-plugin": "^6.0.1", @@ -119,13 +121,17 @@ "postcss-import": "^14.0.2", "postcss-loader": "^6.1.1", "postcss-preset-env": "^7.4.3", - "prettier": "^1.18.2", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", "react-hot-loader": "^4.13.0", "semver": "^7.5.1", "serve": "^14.2.0", "shader-loader": "^1.3.1", + "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": "4.6.4", "unused-webpack-plugin": "2.4.0", @@ -149,7 +155,6 @@ ] }, "resolutions": { - "@cornerstonejs/core": "^1.1.0", "**/@babel/runtime": "^7.20.13", "commander": "8.3.0", "nth-check": "^2.1.1", diff --git a/platform/app/.all-contributorsrc b/platform/app/.all-contributorsrc index ea8f6c9fdc2..b12263a8c7b 100644 --- a/platform/app/.all-contributorsrc +++ b/platform/app/.all-contributorsrc @@ -1,7 +1,5 @@ { - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, "contributors": [ @@ -10,92 +8,70 @@ "name": "Erik Ziegler", "avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4", "profile": "https://github.com/swederik", - "contributions": [ - "code", - "infra" - ] + "contributions": ["code", "infra"] }, { "login": "evren217", "name": "Evren Ozkan", "avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4", "profile": "https://github.com/evren217", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "galelis", "name": "Gustavo André Lelis", "avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4", "profile": "https://github.com/galelis", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dannyrb", "name": "Danny Brown", "avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4", "profile": "http://dannyrb.com/", - "contributions": [ - "code", - "infra" - ] + "contributions": ["code", "infra"] }, { "login": "allcontributors", "name": "allcontributors[bot]", "avatar_url": "https://avatars3.githubusercontent.com/u/46843839?v=4", "profile": "https://github.com/all-contributors/all-contributors-bot", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "EsrefDurna", "name": "Esref Durna", "avatar_url": "https://avatars0.githubusercontent.com/u/1230575?v=4", "profile": "https://www.linkedin.com/in/siliconvalleynextgeneration/", - "contributions": [ - "question" - ] + "contributions": ["question"] }, { "login": "diego0020", "name": "diego0020", "avatar_url": "https://avatars3.githubusercontent.com/u/7297450?v=4", "profile": "https://github.com/diego0020", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dlwire", "name": "David Wire", "avatar_url": "https://avatars3.githubusercontent.com/u/1167291?v=4", "profile": "https://github.com/dlwire", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "jfmedeiros1820", "name": "João Felipe de Medeiros Moreira", "avatar_url": "https://avatars1.githubusercontent.com/u/2211708?v=4", "profile": "https://github.com/jfmedeiros1820", - "contributions": [ - "test" - ] + "contributions": ["test"] }, { "login": "pavertomato", "name": "Egor Lezhnin", "avatar_url": "https://avatars0.githubusercontent.com/u/878990?v=4", "profile": "http://egor.lezhn.in", - "contributions": [ - "code" - ] + "contributions": ["code"] } ], "contributorsPerLine": 7, diff --git a/platform/app/.gitignore b/platform/app/.gitignore new file mode 100644 index 00000000000..e985853ed84 --- /dev/null +++ b/platform/app/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/platform/app/.recipes/Nginx-Dcm4che/docker-compose-dcm4che.env b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/docker-compose-dcm4che.env rename to platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env diff --git a/platform/app/.recipes/Nginx-Dcm4che/docker-compose.yml b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/docker-compose.yml rename to platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml diff --git a/platform/app/.recipes/Nginx-Dcm4che/etc/localtime b/platform/app/.recipes/Nginx-Dcm4chee/etc/localtime similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/etc/localtime rename to platform/app/.recipes/Nginx-Dcm4chee/etc/localtime diff --git a/platform/app/.recipes/Nginx-Dcm4che/etc/timezone b/platform/app/.recipes/Nginx-Dcm4chee/etc/timezone similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/etc/timezone rename to platform/app/.recipes/Nginx-Dcm4chee/etc/timezone diff --git a/platform/app/.recipes/Nginx-Dcm4che/nginx-proxy/conf/nginx.conf b/platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/nginx-proxy/conf/nginx.conf rename to platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore deleted file mode 100644 index 67f1b2e985c..00000000000 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore +++ /dev/null @@ -1,16 +0,0 @@ -# Output -dist/ - -# Dependencies -node_modules/ - -# Root -README.md -Dockerfile - -# Misc. Config -.git -.DS_Store -.gitignore -.vscode -.circleci diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf index 7360c33610f..9e496b79d92 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf @@ -22,7 +22,7 @@ http { # lua_ settings # - lua_package_path '/usr/local/openresty/lualib/?.lua;;'; + lua_package_path '/usr/local/openresty/lualib/?.lua;;/usr/local/share/lua/5.4/?.lua;;'; lua_shared_dict discovery 1m; # cache for discovery metadata documents lua_shared_dict jwks 1m; # cache for JWKs # lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; @@ -182,6 +182,8 @@ http { index index.html; try_files $uri $uri/ /index.html; add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; } # EXAMPLE: Reverse Proxy, no auth diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json index 3f0e6118a6c..4716e1592cd 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json @@ -368,19 +368,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "84e7b84d-ab74-452e-8a24-a0e657f100ea", @@ -406,19 +395,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "f7dd8587-4035-4590-a1c0-ebf576c4dfe3", @@ -462,19 +440,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "f7f76add-411b-420d-9be1-bd120ed99918", @@ -500,19 +467,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "3785434d-2af8-478c-b135-f0b11d1d3205", @@ -598,19 +554,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ], + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"], "authorizationSettings": { "allowRemoteResourceManagement": true, "policyEnforcementMode": "ENFORCING", @@ -678,19 +623,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "53d51818-e1bf-4fc2-aa20-5541f2646f12", @@ -733,19 +667,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] } ], "clientScopes": [ @@ -1233,19 +1156,8 @@ } } ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], + "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins"], + "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml index 771737f3cfe..f3c0da26526 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml @@ -10,9 +10,9 @@ services: ohif_viewer: build: # Project root - context: ./../../ + context: ./../../../../ # Relative to context - dockerfile: ./docker/OpenResty-Orthanc-Keycloak/dockerfile + dockerfile: ./platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile image: webapp:latest container_name: webapp volumes: @@ -35,7 +35,7 @@ services: # TODO: Update to use Postgres # https://github.com/mrts/docker-postgresql-multiple-databases orthanc: - image: jodogne/orthanc-plugins:1.5.6 + image: jodogne/orthanc-plugins hostname: orthanc container_name: orthanc volumes: diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile index 73090e8ae19..fa066291e3d 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile @@ -23,43 +23,63 @@ # Stage 1: Build the application -FROM node:11.2.0-slim as builder +FROM node:18.16.1-slim as builder RUN mkdir /usr/src/app WORKDIR /usr/src/app +RUN apt-get update && apt-get install -y build-essential python3 + ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js ENV PATH /usr/src/app/node_modules/.bin:$PATH -COPY package.json /usr/src/app/package.json -COPY yarn.lock /usr/src/app/yarn.lock +# Copy all files from the root of the OHIF source and note +# that the Docker ignore file at the root (i.e. ./dockerignore) will filter +# out files and directories that are not needed. +COPY ./ /usr/src/app/ ADD . /usr/src/app/ +RUN yarn config set workspaces-experimental true RUN yarn install -RUN yarn run build:web +RUN yarn run build # Stage 2: Bundle the built application into a Docker container # which runs openresty (nginx) using Alpine Linux # LINK: https://hub.docker.com/r/openresty/openresty -FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat +FROM openresty/openresty:1.21.4.2-0-bullseye-fat RUN mkdir /var/log/nginx -RUN apk add --no-cache openssl -RUN apk add --no-cache openssl-dev -RUN apk add --no-cache git -RUN apk add --no-cache gcc +RUN apt-get update && \ + apt-get install -y openssl libssl-dev git gcc wget unzip make&& \ + apt-get clean + +RUN apt-get install --assume-yes lua5.4 libzmq3-dev lua5.4-dev +RUN cd /tmp && \ + wget http://luarocks.org/releases/luarocks-3.9.2.tar.gz && \ + tar zxpf luarocks-3.9.2.tar.gz && \ + cd luarocks-3.9.2 && \ + ./configure && \ + make && \ + make install + # !!! +RUN luarocks install lua-resty-http +# RUN luarocks install lua-nginx-module +RUN luarocks install lua-cjson +RUN luarocks install lua-resty-string +RUN luarocks install lua-resty-session +RUN luarocks install lua-resty-jwt RUN luarocks install lua-resty-openidc +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + # -RUN luarocks install lua-resty-jwt -RUN luarocks install lua-resty-session RUN luarocks install lua-resty-http # !!! -RUN luarocks install lua-resty-openidc -RUN luarocks install luacrypto +RUN luarocks install lua-resty-auto-ssl + # Copy build output to image -COPY --from=builder /usr/src/app/build /var/www/html +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"] diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css index 5b5f0b9bee7..ed1423365c9 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css @@ -71,26 +71,10 @@ input[type='password']:hover { input[type='submit'] { border: none; - background: -webkit-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -moz-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -ms-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -o-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); + background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -ms-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -o-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5); diff --git a/platform/app/.recipes/OpenResty-Orthanc/.dockerignore b/platform/app/.recipes/OpenResty-Orthanc/.dockerignore deleted file mode 100644 index 67f1b2e985c..00000000000 --- a/platform/app/.recipes/OpenResty-Orthanc/.dockerignore +++ /dev/null @@ -1,16 +0,0 @@ -# Output -dist/ - -# Dependencies -node_modules/ - -# Root -README.md -Dockerfile - -# Misc. Config -.git -.DS_Store -.gitignore -.vscode -.circleci diff --git a/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf b/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf index 777e4970364..aff59a13027 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf +++ b/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf @@ -118,6 +118,8 @@ http { index index.html; try_files $uri $uri/ /index.html; add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; } # EXAMPLE: Redirect server error pages to the static page /40x.html diff --git a/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml b/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml index 824ae5a3e1d..16ec1ada705 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml +++ b/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml @@ -34,7 +34,7 @@ services: # TODO: Update to use Postgres # https://github.com/mrts/docker-postgresql-multiple-databases orthanc: - image: jodogne/orthanc-plugins:1.5.6 + image: jodogne/orthanc-plugins hostname: orthanc container_name: orthanc volumes: diff --git a/platform/app/.recipes/OpenResty-Orthanc/dockerfile b/platform/app/.recipes/OpenResty-Orthanc/dockerfile index 768cbc576e7..643c52a57d5 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/dockerfile +++ b/platform/app/.recipes/OpenResty-Orthanc/dockerfile @@ -23,24 +23,19 @@ # Stage 1: Build the application -FROM node:12.22.1-slim as builder +FROM node:18.16.1-slim as builder RUN mkdir /usr/src/app WORKDIR /usr/src/app -# Copy Files -COPY .docker /usr/src/app/.docker -COPY .webpack /usr/src/app/.webpack -COPY extensions /usr/src/app/extensions -COPY modes /usr/src/app/modes -COPY platform /usr/src/app/platform -COPY .browserslistrc /usr/src/app/.browserslistrc -COPY aliases.config.js /usr/src/app/aliases.config.js -COPY babel.config.js /usr/src/app/babel.config.js -COPY lerna.json /usr/src/app/lerna.json -COPY package.json /usr/src/app/package.json -COPY postcss.config.js /usr/src/app/postcss.config.js -COPY yarn.lock /usr/src/app/yarn.lock +# Copy all files from the root of the OHIF source and note +# that the Docker ignore file at the root (i.e. ./dockerignore) will filter +# out files and directories that are not needed. +COPY ./ /usr/src/app/ + +# For arm builds since parcel doesn't have prebuilt binaries for arm yet +RUN apt-get update && apt-get install -y build-essential python3 + # ADD . /usr/src/app/ RUN yarn config set workspaces-experimental true diff --git a/platform/app/.webpack/rules/extractStyleChunks.js b/platform/app/.webpack/rules/extractStyleChunks.js index 10e790e6761..f7c467fc631 100644 --- a/platform/app/.webpack/rules/extractStyleChunks.js +++ b/platform/app/.webpack/rules/extractStyleChunks.js @@ -2,6 +2,20 @@ const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin'); function extractStyleChunks(isProdBuild) { return [ + // If you are using the old stylus, you should uncomment this + // { + // test: /\.styl$/, + // use: [ + // { + // loader: ExtractCssChunksPlugin.loader, + // options: { + // hot: !isProdBuild, + // }, + // }, + // { loader: 'css-loader' }, + // { loader: 'stylus-loader' }, + // ], + // }, { test: /\.(sa|sc|c)ss$/, use: [ @@ -12,7 +26,7 @@ function extractStyleChunks(isProdBuild) { }, }, 'css-loader', - 'postcss-loader' + 'postcss-loader', ], }, ]; diff --git a/platform/app/.webpack/webpack.pwa.js b/platform/app/.webpack/webpack.pwa.js index 0da6104f5e5..af0b70b445e 100644 --- a/platform/app/.webpack/webpack.pwa.js +++ b/platform/app/.webpack/webpack.pwa.js @@ -54,7 +54,7 @@ module.exports = (env, argv) => { path: DIST_DIR, filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js', publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix - devtoolModuleFilenameTemplate: function(info) { + devtoolModuleFilenameTemplate: function (info) { if (isProdBuild) { return `webpack:///${info.resourcePath}`; } else { @@ -102,8 +102,7 @@ module.exports = (env, argv) => { }, // Copy Dicom Microscopy Viewer build files { - from: - '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import', + from: '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import', to: DIST_DIR, globOptions: { ignore: ['**/*.min.js.map'], @@ -111,8 +110,7 @@ module.exports = (env, argv) => { }, // Copy dicom-image-loader build files { - from: - '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', + from: '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', to: DIST_DIR, }, ], @@ -133,6 +131,8 @@ module.exports = (env, argv) => { maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // Need to exclude the theme as it is updated independently exclude: [/theme/], + // Cache large files for the manifests to avoid warning messages + maximumFileSizeToCacheInBytes: 1024 * 1024 * 50, }), ], // https://webpack.js.org/configuration/dev-server/ @@ -186,8 +186,6 @@ module.exports = (env, argv) => { chunkFilename: '[id].css', }) ); - } else { - mergedConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); } return mergedConfig; diff --git a/platform/app/.webpack/writePluginImportsFile.js b/platform/app/.webpack/writePluginImportsFile.js index 41caf84b616..47456d034fa 100644 --- a/platform/app/.webpack/writePluginImportsFile.js +++ b/platform/app/.webpack/writePluginImportsFile.js @@ -7,7 +7,7 @@ const autogenerationDisclaimer = ` // THIS FILE IS AUTOGENERATED AS PART OF THE EXTENSION AND MODE PLUGIN PROCESS. // IT SHOULD NOT BE MODIFIED MANUALLY \n`; -const extractName = (val) => (typeof val === 'string') ? val : val.packageName; +const extractName = val => (typeof val === 'string' ? val : val.packageName); function constructLines(input, categoryName) { let pluginCount = 0; @@ -24,9 +24,7 @@ function constructLines(input, categoryName) { const packageName = extractName(entry); - lines.addToWindowLines.push( - `${categoryName}.push("${packageName}");\n` - ); + lines.addToWindowLines.push(`${categoryName}.push("${packageName}");\n`); pluginCount++; }); @@ -64,7 +62,8 @@ function getRuntimeLoadModesExtensions(modules) { dynamicLoad.push( '\n\n// Add a dynamic runtime loader', 'async function loadModule(module) {', - ' if (typeof module !== \'string\') return module;'); + " if (typeof module !== 'string') return module;" + ); modules.forEach(module => { const packageName = extractName(module); dynamicLoad.push( @@ -73,16 +72,17 @@ function getRuntimeLoadModesExtensions(modules) { ' return imported.default;', ' }' ); - }) + }); dynamicLoad.push( - ' return (await import(module)).default;', + ' return (await import(/* webpackIgnore: true */ module)).default;', '}\n', '// Import a list of items (modules or string names)', '// @return a Promise evaluating to a list of modules', 'export default function importItems(modules) {', ' return Promise.all(modules.map(loadModule));', '}\n', - 'export { loadModule, modes, extensions, importItems };\n\n'); + 'export { loadModule, modes, extensions, importItems };\n\n' + ); return dynamicLoad.join('\n'); } @@ -91,14 +91,9 @@ const fromDirectory = (srcDir, path) => { if (path[0] === '.') return srcDir + '/../../..' + path.substring(1); if (path[0] === '~') return os.homedir() + path.substring(1); return path; -} +}; -const createCopyPluginToDistForLink = ( - srcDir, - distDir, - plugins, - folderName -) => { +const createCopyPluginToDistForLink = (srcDir, distDir, plugins, folderName) => { return plugins .map(plugin => { const fromDir = fromDirectory(srcDir, plugin.directory); @@ -107,7 +102,7 @@ const createCopyPluginToDistForLink = ( return exists ? { from, - to: distDir, + to: distDir, toType: 'dir', } : undefined; @@ -115,12 +110,7 @@ const createCopyPluginToDistForLink = ( .filter(x => !!x); }; -const createCopyPluginToDistForBuild = ( - SRC_DIR, - DIST_DIR, - plugins, - folderName -) => { +const createCopyPluginToDistForBuild = (SRC_DIR, DIST_DIR, plugins, folderName) => { return plugins .map(plugin => { const from = `${SRC_DIR}/../../../node_modules/${plugin.packageName}/${folderName}/`; @@ -156,17 +146,12 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { ...pluginConfig.modes, ]); - fs.writeFileSync( - `${SRC_DIR}/pluginImports.js`, - pluginImportsJsContent, - { flag: 'w+' }, - err => { - if (err) { - console.error(err); - return; - } + fs.writeFileSync(`${SRC_DIR}/pluginImports.js`, pluginImportsJsContent, { flag: 'w+' }, err => { + if (err) { + console.error(err); + return; } - ); + }); // Build packages using cli add-mode and add-extension // will get added to the root node_modules, but the linked packages @@ -175,21 +160,14 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { const copyPluginPublicToDistBuild = createCopyPluginToDistForBuild( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'public' ); const copyPluginPublicToDistLink = createCopyPluginToDistForLink( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ...pluginConfig.public, - ], + [...pluginConfig.modes, ...pluginConfig.extensions, ...pluginConfig.public], 'public' ); @@ -198,20 +176,14 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { const copyPluginDistToDistBuild = createCopyPluginToDistForBuild( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'dist' ); const copyPluginDistToDistLink = createCopyPluginToDistForLink( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'dist' ); diff --git a/platform/app/CHANGELOG.md b/platform/app/CHANGELOG.md index ca7b98371d7..a605e2eafdb 100644 --- a/platform/app/CHANGELOG.md +++ b/platform/app/CHANGELOG.md @@ -3,6 +3,494 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + + +### Bug Fixes + +* **editing:** regression bug in disable editing ([#3687](https://github.com/OHIF/Viewers/issues/3687)) ([4dc2acd](https://github.com/OHIF/Viewers/commit/4dc2acdefa872dd1d8df47f465e9e9656f95f67f)) + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Bug Fixes + +* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a)) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + + +### Bug Fixes + +* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11)) + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) + + + + + # [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14) diff --git a/platform/app/babel.config.js b/platform/app/babel.config.js index fed6f05fecd..325ca2a8ee7 100644 --- a/platform/app/babel.config.js +++ b/platform/app/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/app/cypress.config.ts b/platform/app/cypress.config.ts index 7cf5ff77f98..c575e7c56cd 100644 --- a/platform/app/cypress.config.ts +++ b/platform/app/cypress.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ responseTimeout: 10000, specPattern: 'cypress/integration/**/*.spec.[jt]s', projectId: '4oe38f', - video: false, + video: true, reporter: 'junit', reporterOptions: { mochaFile: 'cypress/results/test-output.xml', diff --git a/platform/app/cypress/fixtures/example.json b/platform/app/cypress/fixtures/example.json index da18d9352a1..02e4254378e 100644 --- a/platform/app/cypress/fixtures/example.json +++ b/platform/app/cypress/fixtures/example.json @@ -2,4 +2,4 @@ "name": "Using fixtures to represent data", "email": "hello@cypress.io", "body": "Fixtures are a great way to mock data for responses to routes" -} \ No newline at end of file +} diff --git a/platform/app/cypress/integration/MultiStudy.spec.js b/platform/app/cypress/integration/MultiStudy.spec.js new file mode 100644 index 00000000000..43fec70cfdf --- /dev/null +++ b/platform/app/cypress/integration/MultiStudy.spec.js @@ -0,0 +1,26 @@ +describe('OHIF Multi Study', () => { + const beforeSetup = () => { + cy.initViewer( + '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1,1.2.840.113619.2.5.1762583153.215519.978957063.78', + { + params: '&hangingProtocolId=@ohif/hpCompare', + minimumThumbnails: 3, + } + ); + }; + + it('Should display 2 comparison up', () => { + beforeSetup(); + + cy.get('[data-cy="viewport-pane"]').as('viewportPane'); + cy.get('@viewportPane').its('length').should('be.eq', 4); + + cy.get('[data-cy="studyDate"]').as('studyDate'); + + cy.get('@studyDate').should(studyDate => { + expect(studyDate.length).to.be.eq(4); + expect(studyDate.text()).to.contain('2014').contain('2001'); + expect(studyDate.text().indexOf('2014')).to.be.lessThan(studyDate.text().indexOf('2001')); + }); + }); +}); diff --git a/platform/app/cypress/integration/OHIFPdfDisplay.spec.js b/platform/app/cypress/integration/OHIFPdfDisplay.spec.js index 8027380f660..2ef2f020efe 100644 --- a/platform/app/cypress/integration/OHIFPdfDisplay.spec.js +++ b/platform/app/cypress/integration/OHIFPdfDisplay.spec.js @@ -1,13 +1,9 @@ -describe('OHIF PDF Display', function() { - beforeEach(function() { +describe('OHIF PDF Display', function () { + beforeEach(function () { cy.openStudyInViewer('2.25.317377619501274872606137091638706705333'); - - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail-no-image"]') - .its('length') - .should('be.gt', 0); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail-no-image"]').its('length').should('be.gt', 0); }); }); diff --git a/platform/app/cypress/integration/OHIFVideoDisplay.spec.js b/platform/app/cypress/integration/OHIFVideoDisplay.spec.js index e312144ee90..ff877034aff 100644 --- a/platform/app/cypress/integration/OHIFVideoDisplay.spec.js +++ b/platform/app/cypress/integration/OHIFVideoDisplay.spec.js @@ -1,19 +1,14 @@ -describe('OHIF Video Display', function() { - beforeEach(function() { +describe('OHIF Video Display', function () { + beforeEach(function () { cy.openStudyInViewer('2.25.96975534054447904995905761963464388233'); - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail-no-image"]') - .its('length') - .should('be.gt', 1); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail-no-image"]').its('length').should('be.gt', 1); }); it('performs double-click to load thumbnail in active viewport', () => { - cy.get( - '[data-cy="study-browser-thumbnail-no-image"]:nth-child(2)' - ).dblclick(); + cy.get('[data-cy="study-browser-thumbnail-no-image"]:nth-child(2)').dblclick(); //const expectedText = 'Ser: 3'; //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); diff --git a/platform/app/cypress/integration/customization/HangingProtocol.spec.js b/platform/app/cypress/integration/customization/HangingProtocol.spec.js index e57fd7d038c..4f55e620b59 100644 --- a/platform/app/cypress/integration/customization/HangingProtocol.spec.js +++ b/platform/app/cypress/integration/customization/HangingProtocol.spec.js @@ -1,5 +1,5 @@ describe('OHIF HP', () => { - const beforeSetup = () => { + beforeEach(() => { cy.checkStudyRouteInViewer( '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1', '&hangingProtocolId=@ohif/mnGrid' @@ -7,30 +7,21 @@ describe('OHIF HP', () => { cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); - }; + cy.waitDicomImage(); + }); it('Should display 3 up', () => { - beforeSetup(); - - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 3); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 3); }); it('Should navigate next/previous stage', () => { - beforeSetup(); - cy.get('body').type(','); cy.wait(250); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 4); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 4); cy.get('body').type('..'); cy.wait(250); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 2); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 2); }); it('Should navigate to display set specified', () => { diff --git a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js index 7b46387faf6..431ff8b4ec6 100644 --- a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js +++ b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js @@ -11,17 +11,21 @@ describe('OHIF Double Click', () => { it('Should double click each viewport to one up and back', () => { const numExpectedViewports = 3; - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', numExpectedViewports); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', numExpectedViewports); for (let i = 0; i < numExpectedViewports; i += 1) { + cy.wait(2000); + // For whatever reason, with Cypress tests, we have to activate the // viewport we are double clicking first. cy.get('[data-cy="viewport-pane"]') .eq(i) - .trigger('mousedown', 'center', { force: true }) - .trigger('mouseup', 'center', { force: true }); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); // Wait for the viewport to be 'active'. // TODO Is there a better way to do this? @@ -32,21 +36,19 @@ describe('OHIF Double Click', () => { .not('.pointer-events-none'); // The actual double click. - cy.get('[data-cy="viewport-pane"]') - .eq(i) - .trigger('dblclick', 'center'); + cy.get('[data-cy="viewport-pane"]').eq(i).trigger('dblclick', 'center'); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 1); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 1); cy.get('[data-cy="viewport-pane"]') - .eq(0) - .trigger('dblclick', 'center'); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', numExpectedViewports); + cy.get('[data-cy="viewport-pane"]').eq(0).trigger('dblclick', 'center'); } }); }); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js index e87f1bb80c3..7e6806413f3 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js @@ -1,20 +1,18 @@ describe('OHIF Context Menu', function () { beforeEach(function () { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); + cy.waitDicomImage(); }); it('checks context menu customization', function () { // Add length measurement cy.addLengthMeasurement(); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); - cy.get('[data-cy="measurement-item"]').click(); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); + cy.get('[data-cy="measurement-item"]').as('measurementItem').click(); const [x1, y1] = [150, 100]; cy.get('@viewport') @@ -26,17 +24,12 @@ describe('OHIF Context Menu', function () { }); // Contextmenu is visible - cy.get('[data-cy="context-menu"]').should('be.visible'); - + cy.get('[data-cy="context-menu"]').as('contextMenu').should('be.visible'); // Click "Finding" subMenu - cy.get('[data-cy="context-menu-item"]') - .contains('Finding') - .click(); + cy.get('[data-cy="context-menu-item"]').as('item').contains('Finding').click(); // Click "Finding" subMenu - cy.get('[data-cy="context-menu-item"]') - .contains('Aortic insufficiency') - .click(); - cy.get('[data-cy="measurement-item"]').contains('Aortic insufficiency'); + cy.get('[data-cy="context-menu-item"]').as('item').contains('Aortic insufficiency').click(); + cy.get('[data-cy="measurement-item"]').as('measure-item').contains('Aortic insufficiency'); }); }); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js index ddee809cac2..984b038bc0e 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js @@ -1,26 +1,22 @@ describe('OHIF Cornerstone Hotkeys', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.window() .its('cornerstone') .then(cornerstone => { // For debugging issues where tests pass locally but fail on CI // - Sometimes Cypress orb seems to use CPU rendering pathway - cy.log( - `Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}` - ); + cy.log(`Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}`); }); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); + cy.waitDicomImage(); }); it('checks if hotkeys "R" and "L" can rotate the image', () => { - // Hotkey R cy.get('body').type('R'); cy.get('@viewportInfoMidLeft').should('contains.text', 'P'); cy.get('@viewportInfoMidTop').should('contains.text', 'R'); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js index c20aa75c63c..e0c509b972e 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js @@ -1,18 +1,15 @@ describe('OHIF Cornerstone Toolbar', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); - cy.get('[data-cy="study-browser-thumbnail"]') - .eq(1) - .click(); + cy.get('[data-cy="study-browser-thumbnail"]').eq(1).click(); //const expectedText = 'Ser: 1'; //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); + cy.waitDicomImage(); }); it('checks if all primary buttons are being displayed', () => { @@ -70,12 +67,12 @@ describe('OHIF Cornerstone Toolbar', () => { // }); it('checks if Levels tool will change the window width and center of an image', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@wwwcBtnPrimary') - .click() - .then($wwwcBtn => { - cy.wrap($wwwcBtn).should('have.class', 'active'); - }); + // Wait for the DICOM image to load + + // Assign an alias to the button element + cy.get('@wwwcBtnPrimary').as('wwwcButton'); + cy.get('@wwwcButton').click(); + cy.get('@wwwcButton').should('have.class', 'active'); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -88,20 +85,21 @@ describe('OHIF Cornerstone Toolbar', () => { // The exact text is slightly dependent on the viewport resolution, so leave a range cy.get('@viewportInfoTopLeft').should($txt => { const text = $txt.text(); - expect(text) - .to.include('W:193') - .include('L:479'); + expect(text).to.include('W:193').include('L:479'); }); }); it('checks if Pan tool will move the image inside the viewport', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@panBtn') - .click() - .then($panBtn => { - cy.wrap($panBtn).should('have.class', 'active'); - }); + // Assign an alias to the button element + cy.get('@panBtn').as('panButton'); + // Click on the button + cy.get('@panButton').click(); + + // Assert that the button has the 'active' class + cy.get('@panButton').should('have.class', 'active'); + + // Trigger the pan actions on the viewport cy.get('@viewport') .trigger('mousedown', 'center', { buttons: 1 }) .trigger('mousemove', 'bottom', { buttons: 1 }) @@ -111,16 +109,14 @@ describe('OHIF Cornerstone Toolbar', () => { it('checks if Length annotation can be added to viewport and shows up in the measurements panel', () => { //Click on button and verify if icon is active on toolbar cy.addLengthMeasurement(); - cy.get('[data-cy="viewport-notification"]').should('exist'); - cy.get('[data-cy="viewport-notification"]').should('be.visible'); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); + cy.get('[data-cy="viewport-notification"]').as('notif').should('exist'); + cy.get('[data-cy="viewport-notification"]').as('notif').should('be.visible'); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); //Verify the measurement exists in the table cy.get('@measurementsPanel').should('be.visible'); - cy.get('[data-cy="measurement-item"]') - .its('length') - .should('be.at.least', 1); + cy.get('[data-cy="measurement-item"]').as('measure').its('length').should('be.at.least', 1); }); /*it('checks if angle annotation can be added on viewport without causing any errors', () => { @@ -413,20 +409,42 @@ describe('OHIF Cornerstone Toolbar', () => { cy.get('@moreBtn').click(); cy.get('.tooltip-toolbar-overlay').should('not.exist'); }); +*/ + it('check if Flip tool will flip the image in the viewport', () => { + cy.get('@viewportInfoMidLeft').should('contains.text', 'R'); + cy.get('@viewportInfoMidTop').should('contains.text', 'A'); - it('check if Flip V tool will flip the image vertically in the viewport', () => { //Click on More button - cy.get('@moreBtn').click(); - //Verify if overlay is displayed - cy.get('.tooltip-toolbar-overlay').should('be.visible'); + cy.get('@moreBtnSecondary').click(); - //Click on Flip V button - cy.get('[data-cy="flip v"]').click(); - cy.get('@viewportInfoMidLeft').should('contains.text', 'R'); - cy.get('@viewportInfoMidTop').should('contains.text', 'F'); + //Click on Flip button + cy.get('[data-cy="flip-horizontal"]').click(); + cy.waitDicomImage(); + cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); + cy.get('@viewportInfoMidTop').should('contains.text', 'A'); + }); - //Click on More button to close it - cy.get('@moreBtn').click(); - cy.get('.tooltip-toolbar-overlay').should('not.exist'); - });*/ + it('checks if stack sync is preserved on new display set and uses FOR', () => { + // Active stack image sync and reference lines + cy.get('[data-cy="MoreTools-split-button-secondary"]').click(); + cy.get('[data-cy="StackImageSync"]').click(); + // Add reference lines as that sometimes throws an exception + cy.get('[data-cy="MoreTools-split-button-secondary"]').click(); + cy.get('[data-cy="ReferenceLines"]').click(); + + cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick(); + cy.get('body').type('{downarrow}{downarrow}'); + + // Change the layout and double load the first + cy.setLayout(2, 1); + cy.get('body').type('{rightarrow}'); + cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick(); + cy.waitDicomImage(); + + // Now navigate down once and check that the left hand pane navigated + cy.get('body').type('{downarrow}'); + cy.get('body').type('{leftarrow}'); + cy.setLayout(1, 1); + cy.get('@viewportInfoTopRight').should('contains.text', 'I:2 (2/20)'); + }); }); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js index 9c3752e7654..35ef9869252 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js @@ -1,13 +1,11 @@ describe('OHIF Download Snapshot File', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.openDownloadImageModal(); }); - it('checks displayed information for Desktop experience', function() { + it('checks displayed information for Desktop experience', function () { // Set Desktop resolution // cy.viewport(1750, 720); // Visual comparison @@ -36,12 +34,10 @@ describe('OHIF Download Snapshot File', () => { // .and('include', 'data:image'); // Check buttons - cy.get('[data-cy="cancel-btn"]') - .scrollIntoView() - .should('be.visible'); - cy.get('[data-cy="download-btn"]') - .scrollIntoView() - .should('be.visible'); + cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible'); + cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible'); + + cy.get('[data-cy="cancel-btn"]').click(); }); /*it('cancel changes on download modal', function() { diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js index 67c1597d909..420cdd82262 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js @@ -1,21 +1,17 @@ -describe('OHIF Study Viewer Page', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF General Viewer', function () { + beforeEach(() => + cy.initViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78', { + minimumThumbnails: 3, + }) + ); - cy.expectMinimumThumbnails(3); - cy.initCommonElementsAliases(); - cy.initCornerstoneToolsAliases(); - }); - - it('scrolls series stack using scrollbar', function() { + it('scrolls series stack using scrollbar', function () { cy.scrollToIndex(13); cy.get('@viewportInfoTopRight').should('contains.text', '14'); }); - it('performs right click to zoom', function() { + it('performs right click to zoom', function () { // This is not used to activate the tool, it is used to ensure the // top left viewport info shows the zoom values (it only shows up // when the zoom tool is active) @@ -25,11 +21,9 @@ describe('OHIF Study Viewer Page', function() { cy.wrap($zoomBtn).should('have.class', 'active'); }); - const zoomLevelInitial = cy - .get('@viewportInfoTopLeft') - .then($viewportInfo => { - return $viewportInfo.text().substring(6, 9); - }); + const zoomLevelInitial = cy.get('@viewportInfoTopLeft').then($viewportInfo => { + return $viewportInfo.text().substring(6, 9); + }); //Right click on viewport cy.get('@viewport') diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js index c96d777010f..9f0e8b0c8d9 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js @@ -1,16 +1,14 @@ -describe('OHIF Measurement Panel', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF Measurement Panel', function () { + beforeEach(function () { + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); + cy.waitDicomImage(); }); - it('checks if Measurements right panel can be hidden/displayed', function() { + it('checks if Measurements right panel can be hidden/displayed', function () { cy.get('@measurementsPanel').should('exist'); cy.get('@measurementsPanel').should('be.visible'); @@ -22,31 +20,31 @@ describe('OHIF Measurement Panel', function() { cy.get('@measurementsPanel').should('be.visible'); }); - it('checks if measurement item can be Relabeled under Measurements panel', function() { + it('checks if measurement item can be Relabeled under Measurements panel', function () { // Add length measurement cy.addLengthMeasurement(); - cy.get('[data-cy="viewport-notification"]').should('exist'); - cy.get('[data-cy="viewport-notification"]').should('be.visible'); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); - cy.get('[data-cy="measurement-item"]').click(); - cy.get('[data-cy="measurement-item"]') - .find('svg') - .click(); + cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('exist'); + cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('be.visible'); + + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); + + cy.get('[data-cy="measurement-item"]').as('measurementItem').click(); + + cy.get('[data-cy="measurement-item"]').find('svg').as('measurementItemSvg').click(); // enter Bone label cy.get('[data-cy="input-annotation"]').should('exist'); cy.get('[data-cy="input-annotation"]').should('be.visible'); cy.get('[data-cy="input-annotation"]').type('Bone{enter}'); - // Verify if 'Bone' label was added - cy.get('[data-cy="measurement-item"]').should('contain.text', 'Bone'); + cy.get('[data-cy="measurement-item"]').as('measurementItem').should('contain.text', 'Bone'); }); - it('checks if image would jump when clicked on a measurement item', function() { + it('checks if image would jump when clicked on a measurement item', function () { // Add length measurement - cy.addLengthMeasurement(); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); + cy.addLengthMeasurement().wait(250); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); cy.scrollToIndex(13); @@ -55,9 +53,7 @@ describe('OHIF Measurement Panel', function() { cy.get('@viewportInfoTopRight').should('contains.text', '(14/'); // Click on first measurement item - cy.get('[data-cy="measurement-item"]') - .eq(0) - .click(); + cy.get('[data-cy="measurement-item"]').eq(0).click(); cy.get('@viewportInfoTopRight').should('contains.text', '(1/'); cy.get('@viewportInfoTopRight').should('not.contains.text', '(14/'); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js index e9c70f2eb39..c43cb2c93ac 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js @@ -1,22 +1,17 @@ -describe('OHIF Study Viewer Page', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF Study Browser', function () { + beforeEach(function () { + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail"]') - .its('length') - .should('be.gt', 1); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail"]').its('length').should('be.gt', 1); }); - it('drags and drop a series thumbnail into viewport', function() { + it('drags and drop a series thumbnail into viewport', function () { // Can't use the native drag version as the element should be rerendered // cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged // .drag('.cornerstone-canvas'); //dropzone element @@ -38,7 +33,7 @@ describe('OHIF Study Viewer Page', function() { //cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText); }); - it('checks if Series left panel can be hidden/displayed', function() { + it('checks if Series left panel can be hidden/displayed', function () { cy.get('@seriesPanel').should('exist'); cy.get('@seriesPanel').should('be.visible'); diff --git a/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js b/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js index 0d07b52a52d..9809a1b7c51 100644 --- a/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js +++ b/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js @@ -1,8 +1,8 @@ //We are keeping the hardcoded results values for the study list tests -//this is intended to be running in a controled docker environment with test data. -describe('OHIF Study List', function() { - context('Desktop resolution', function() { - beforeEach(function() { +//this is intended to be running in a controlled docker environment with test data. +describe('OHIF Study List', function () { + context('Desktop resolution', function () { + beforeEach(function () { cy.openStudyList(); cy.viewport(1750, 720); @@ -14,7 +14,7 @@ describe('OHIF Study List', function() { cy.get('@StudyDescription').clear(); }); - it('Displays several studies initially', function() { + it('Displays several studies initially', function () { cy.waitStudyList(); cy.get('@searchResult2').should($list => { expect($list.length).to.be.greaterThan(1); @@ -23,7 +23,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Patient Name with exact string', function() { + it('searches Patient Name with exact string', function () { cy.get('@PatientName').type('Juno'); //Wait result list to be displayed cy.waitStudyList(); @@ -33,7 +33,7 @@ describe('OHIF Study List', function() { }); }); - it('searches MRN with exact string', function() { + it('searches MRN with exact string', function () { cy.get('@MRN').type('0000003'); //Wait result list to be displayed cy.waitStudyList(); @@ -43,7 +43,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Accession with exact string', function() { + it('searches Accession with exact string', function () { cy.get('@AccessionNumber').type('321'); //Wait result list to be displayed cy.waitStudyList(); @@ -53,7 +53,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Description with exact string', function() { + it('searches Description with exact string', function () { cy.get('@StudyDescription').type('PETCT'); //Wait result list to be displayed cy.waitStudyList(); diff --git a/platform/app/cypress/integration/volume/MPR.spec.js b/platform/app/cypress/integration/volume/MPR.spec.js index 080f8100a0e..c2f8d94ccde 100644 --- a/platform/app/cypress/integration/volume/MPR.spec.js +++ b/platform/app/cypress/integration/volume/MPR.spec.js @@ -1,8 +1,6 @@ describe('OHIF MPR', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1' - ); + cy.checkStudyRouteInViewer('1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); @@ -66,19 +64,17 @@ describe('OHIF MPR', () => { .then(cornerstone => { const viewports = cornerstone.getRenderingEngines()[0].getViewports(); - const imageData1 = viewports[0].getImageData(); - const imageData2 = viewports[1].getImageData(); - const imageData3 = viewports[2].getImageData(); + // The stack viewport still exists after the changes to viewportId and inde + const imageData1 = viewports[1].getImageData(); + const imageData2 = viewports[2].getImageData(); + const imageData3 = viewports[3].getImageData(); // for some reason map doesn't work here cy.wrap(imageData1).should('not.be', undefined); cy.wrap(imageData2).should('not.be', undefined); cy.wrap(imageData3).should('not.be', undefined); - cy.wrap(imageData1.dimensions).should( - 'deep.equal', - imageData2.dimensions - ); + cy.wrap(imageData1.dimensions).should('deep.equal', imageData2.dimensions); cy.wrap(imageData1.origin).should('deep.equal', imageData2.origin); }); @@ -88,10 +84,7 @@ describe('OHIF MPR', () => { cy.get('.cornerstone-canvas').should('have.length', 1); // should not have any div under it - cy.get('[data-cy="thumbnail-viewport-labels"]') - .eq(2) - .find('div') - .should('have.length', 0); + cy.get('[data-cy="thumbnail-viewport-labels"]').eq(2).find('div').should('have.length', 0); }); it('should correctly render Crosshairs for MPR', () => { diff --git a/platform/app/cypress/plugins/index.js b/platform/app/cypress/plugins/index.js index aa9918d2153..8dd144a6c1a 100644 --- a/platform/app/cypress/plugins/index.js +++ b/platform/app/cypress/plugins/index.js @@ -18,4 +18,4 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config -} +}; diff --git a/platform/app/cypress/support/DragSimulator.js b/platform/app/cypress/support/DragSimulator.js index 673a4668f7d..c6a8e5f9438 100644 --- a/platform/app/cypress/support/DragSimulator.js +++ b/platform/app/cypress/support/DragSimulator.js @@ -6,18 +6,12 @@ export const DragSimulator = { counter: 0, rectsEqual(r1, r2) { return ( - r1.top === r2.top && - r1.right === r2.right && - r1.bottom === r2.bottom && - r1.left === r2.left + r1.top === r2.top && r1.right === r2.right && r1.bottom === r2.bottom && r1.left === r2.left ); }, get dropped() { const currentSourcePosition = this.source.getBoundingClientRect(); - return !this.rectsEqual( - this.initialSourcePosition, - currentSourcePosition - ); + return !this.rectsEqual(this.initialSourcePosition, currentSourcePosition); }, get hasTriesLeft() { return this.counter < this.MAX_TRIES; @@ -71,8 +65,6 @@ export const DragSimulator = { simulate(sourceWrapper, targetSelector, position = 'center') { return cy .get(targetSelector) - .then(targetWrapper => - this.init(sourceWrapper.get(0), targetWrapper.get(0), position) - ); + .then(targetWrapper => this.init(sourceWrapper.get(0), targetWrapper.get(0), position)); }, }; diff --git a/platform/app/cypress/support/aliases.js b/platform/app/cypress/support/aliases.js index 52dac3ae52c..1bda29b36a2 100644 --- a/platform/app/cypress/support/aliases.js +++ b/platform/app/cypress/support/aliases.js @@ -3,16 +3,10 @@ export function initCornerstoneToolsAliases() { cy.get('[data-cy="StackScroll"]').as('stackScrollBtn'); cy.get('[data-cy="Zoom"]').as('zoomBtn'); cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary'); - cy.get('[data-cy="WindowLevel-split-button-secondary"]').as( - 'wwwcBtnSecondary' - ); + cy.get('[data-cy="WindowLevel-split-button-secondary"]').as('wwwcBtnSecondary'); cy.get('[data-cy="Pan"]').as('panBtn'); - cy.get('[data-cy="MeasurementTools-split-button-primary"]').as( - 'measurementToolsBtnPrimary' - ); - cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as( - 'measurementToolsBtnSecondary' - ); + cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary'); + cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary'); // cy.get('[data-cy="Angle"]').as('angleBtn'); cy.get('[data-cy="MoreTools-split-button-primary"]').as('moreBtnPrimary'); cy.get('[data-cy="MoreTools-split-button-secondary"]').as('moreBtnSecondary'); @@ -37,12 +31,8 @@ export function initCommonElementsAliases() { cy.get('[data-cy="studyBrowser-panel"]').as('seriesPanel'); cy.get('[data-cy="viewport-overlay-top-right"]').as('viewportInfoTopRight'); cy.get('[data-cy="viewport-overlay-top-left"]').as('viewportInfoTopLeft'); - cy.get('[data-cy="viewport-overlay-bottom-right"]').as( - 'viewportInfoBottomRight' - ); - cy.get('[data-cy="viewport-overlay-bottom-left"]').as( - 'viewportInfoBottomLeft' - ); + cy.get('[data-cy="viewport-overlay-bottom-right"]').as('viewportInfoBottomRight'); + cy.get('[data-cy="viewport-overlay-bottom-left"]').as('viewportInfoBottomLeft'); cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft'); cy.get('.top-mid.orientation-marker').as('viewportInfoMidTop'); @@ -50,9 +40,7 @@ export function initCommonElementsAliases() { //Creating aliases for Routes export function initRouteAliases() { - cy.intercept('GET', '**/series**', { statusCode: 200, body: [] }).as( - 'getStudySeries' - ); + cy.intercept('GET', '**/series**', { statusCode: 200, body: [] }).as('getStudySeries'); // Todo: for some reason cypress does not redirect to the correct url // so we intercept the request and redirect it to the correct url @@ -73,7 +61,7 @@ export function initStudyListAliasesOnDesktop() { cy.get('[data-cy="study-list-results"] > tr').as('searchResult2'); // We can't use data attributes (e.g. data--cy) for these since - // they are using third party libraires (i.e. react-dates, react-select) + // they are using third party libraries (i.e. react-dates, react-select) cy.get('#date-range-studyDate-start-date').as('studyListStartDate'); cy.get('#date-range-studyDate-end-date').as('studyListEndDate'); cy.get('#input-modalities').as('modalities'); diff --git a/platform/app/cypress/support/commands.js b/platform/app/cypress/support/commands.js index df760db1035..83b2df9193a 100644 --- a/platform/app/cypress/support/commands.js +++ b/platform/app/cypress/support/commands.js @@ -46,7 +46,7 @@ Cypress.Commands.add('openStudy', PatientName => { cy.openStudyList(); cy.get('#filter-patientNameOrId').type(PatientName); // cy.get('@getStudies').then(() => { - cy.wait(1000); + cy.waitQueryList(); cy.get('[data-cy="study-list-results"]', { timeout: 5000 }) .contains(PatientName) @@ -56,28 +56,41 @@ Cypress.Commands.add('openStudy', PatientName => { Cypress.Commands.add( 'checkStudyRouteInViewer', - (StudyInstanceUID, otherParams = '') => { + (StudyInstanceUID, otherParams = '', mode = '/basic-test') => { cy.location('pathname').then($url => { cy.log($url); - if ( - $url == 'blank' || - !$url.includes(`/basic-test/${StudyInstanceUID}${otherParams}`) - ) { - cy.openStudyInViewer(StudyInstanceUID, otherParams); + if ($url === 'blank' || !$url.includes(`${mode}/${StudyInstanceUID}${otherParams}`)) { + cy.openStudyInViewer(StudyInstanceUID, otherParams, mode); cy.waitDicomImage(); - cy.wait(2000); + // Very short wait to ensure pending updates are handled + cy.wait(25); } }); } ); +Cypress.Commands.add('initViewer', (StudyInstanceUID, other = {}) => { + const { mode = '/basic-test', minimumThumbnails = 1, params = '' } = other; + cy.openStudyInViewer(StudyInstanceUID, params, mode); + cy.waitDicomImage(); + // Very short wait to ensure pending updates are handled + cy.wait(25); + + cy.expectMinimumThumbnails(minimumThumbnails); + cy.initCommonElementsAliases(); + cy.initCornerstoneToolsAliases(); +}); + Cypress.Commands.add( 'openStudyInViewer', - (StudyInstanceUID, otherParams = '') => { - cy.visit(`/basic-test?StudyInstanceUIDs=${StudyInstanceUID}${otherParams}`); + (StudyInstanceUID, otherParams = '', mode = '/basic-test') => { + cy.visit(`${mode}?StudyInstanceUIDs=${StudyInstanceUID}${otherParams}`); } ); +Cypress.Commands.add('waitQueryList', () => { + cy.get('[data-querying="false"]'); +}); /** * Command to search for a Modality and open the study. * @@ -87,14 +100,9 @@ Cypress.Commands.add('openStudyModality', Modality => { cy.initRouteAliases(); cy.visit('/'); - cy.get('#filter-accessionOrModalityOrDescription') - .type(Modality) - .wait(2000); + cy.get('#filter-accessionOrModalityOrDescription').type(Modality).waitQueryList(); - cy.get('[data-cy="study-list-results"]') - .contains(Modality) - .first() - .click(); + cy.get('[data-cy="study-list-results"]').contains(Modality).first().click(); }); /** @@ -113,7 +121,7 @@ Cypress.Commands.add('openStudyList', () => { // For some reason cypress 12.x does not like to stub the network request // so we just wait herer for 1 second // cy.wait('@getStudies'); - cy.wait(1000); + cy.waitQueryList(); }); Cypress.Commands.add('waitStudyList', () => { @@ -140,126 +148,72 @@ Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) => ); /** - * Command to perform two clicks into two different positions. Each position must be [x, y]. + * Command to perform three clicks into three different positions. Each position must be [x, y]. * The positions are considering the element as reference, therefore, top-left of the element will be (0, 0). * * @param {*} viewport - Selector for viewport we would like to interact with * @param {number[]} firstClick - Click position [x, y] * @param {number[]} secondClick - Click position [x, y] + * @param {number[]} thirdClick - Click position [x, y] */ -Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => { +Cypress.Commands.add('addAngle', (viewport, firstClick, secondClick, thirdClick) => { cy.get(viewport).then($viewport => { const [x1, y1] = firstClick; const [x2, y2] = secondClick; + const [x3, y3] = thirdClick; - // TODO: Added a wait which appears necessary in Cornerstone Tools >4? cy.wrap($viewport) - .click(x1, y1) - .wait(100) + .click(x1, y1, { force: true }) .trigger('mousemove', { clientX: x2, clientY: y2 }) - .click(x2, y2) - .wait(100); + .click(x2, y2, { force: true }) + .trigger('mousemove', { clientX: x3, clientY: y3 }) + .click(x3, y3, { force: true }); }); }); -/** - * Command to perform three clicks into three different positions. Each position must be [x, y]. - * The positions are considering the element as reference, therefore, top-left of the element will be (0, 0). - * - * @param {*} viewport - Selector for viewport we would like to interact with - * @param {number[]} firstClick - Click position [x, y] - * @param {number[]} secondClick - Click position [x, y] - * @param {number[]} thirdClick - Click position [x, y] - */ -Cypress.Commands.add( - 'addAngle', - (viewport, firstClick, secondClick, thirdClick) => { - cy.get(viewport).then($viewport => { - const [x1, y1] = firstClick; - const [x2, y2] = secondClick; - const [x3, y3] = thirdClick; - - cy.wrap($viewport) - .click(x1, y1, { force: true }) - .trigger('mousemove', { clientX: x2, clientY: y2 }) - .click(x2, y2, { force: true }) - .trigger('mousemove', { clientX: x3, clientY: y3 }) - .click(x3, y3, { force: true }); - }); - } -); - Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => { cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should( - $itemList => { - expect($itemList.length >= seriesToWait).to.be.true; - } + 'have.length.gte', + seriesToWait ); }); //Command to wait DICOM image to load into the viewport -Cypress.Commands.add('waitDicomImage', (timeout = 50000) => { - const loaded = cy.isPageLoaded(); - - if (loaded) { - cy.window() - .its('cornerstone') - .then({ timeout }, $cornerstone => { - return new Cypress.Promise(resolve => { - const onEvent = renderedEvt => { - const element = renderedEvt.detail.element; - - element.removeEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - $cornerstone.eventTarget.removeEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - resolve(); - }; - const onEnabled = enabledEvt => { - const element = enabledEvt.detail.element; - - element.addEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - - $cornerstone.eventTarget.removeEventListener( - $cornerstone.Enums.Events.ELEMENT_ENABLED, - onEnabled - ); - }; - const enabledElements = $cornerstone.getEnabledElements(); - if (enabledElements && enabledElements.length) { - // Sometimes the page finishes rendering before this gets run, - // if so, just resolve immediately. - resolve(); - } else { - $cornerstone.eventTarget.addEventListener( - $cornerstone.Enums.Events.ELEMENT_ENABLED, - onEnabled - ); +Cypress.Commands.add('waitDicomImage', (mode = '/basic-test', timeout = 50000) => { + cy.window() + .its('cornerstone') + .should($cornerstone => { + const enabled = $cornerstone.getEnabledElements(); + if (enabled?.length) { + enabled.forEach((item, i) => { + if (item.viewport.viewportStatus !== $cornerstone.Enums.ViewportStatus.RENDERED) { + throw new Error(`Viewport ${i} in state ${item.viewport.viewportStatus}`); } }); - }); - } + } else { + throw new Error('No enabled elements'); + } + }); + // This shouldn't be necessary, but seems to be. + cy.wait(250); + cy.log('DICOM image loaded'); }); //Command to reset and clear all the changes made to the viewport Cypress.Commands.add('resetViewport', () => { - //Click on More button + // Assign an alias to the More button cy.get('[data-cy="MoreTools-split-button-primary"]') .should('have.attr', 'data-tool', 'Reset') - .as('moreBtn') - .click(); + .as('moreBtn'); + + // Use the alias to click on the More button + cy.get('@moreBtn').click(); }); Cypress.Commands.add('imageZoomIn', () => { cy.initCornerstoneToolsAliases(); cy.get('@zoomBtn').click(); + cy.wait(25); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -271,6 +225,7 @@ Cypress.Commands.add('imageZoomIn', () => { Cypress.Commands.add('imageContrast', () => { cy.initCornerstoneToolsAliases(); cy.get('@wwwcBtnPrimary').click(); + cy.wait(25); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -303,14 +258,24 @@ Cypress.Commands.add('initStudyListAliasesOnDesktop', () => { Cypress.Commands.add( 'addLengthMeasurement', (firstClick = [150, 100], secondClick = [130, 170]) => { - cy.get('@measurementToolsBtnPrimary') - .should('have.attr', 'data-tool', 'Length') - .click() - .then($lengthBtn => { - cy.wrap($lengthBtn).should('have.class', 'active'); - }); - - cy.addLine('.viewport-element', firstClick, secondClick); + // Assign an alias to the button element + cy.get('@measurementToolsBtnPrimary').as('lengthButton'); + + cy.get('@lengthButton').should('have.attr', 'data-tool', 'Length'); + cy.get('@lengthButton').click(); + + cy.get('@lengthButton').should('have.class', 'active'); + + cy.get('@viewport').then($viewport => { + const [x1, y1] = firstClick; + const [x2, y2] = secondClick; + + cy.wrap($viewport) + .click(x1, y1, { force: true }) + .wait(1000) + .click(x2, y2, { force: true }) + .wait(1000); + }); } ); @@ -318,8 +283,10 @@ Cypress.Commands.add( Cypress.Commands.add( 'addAngleMeasurement', (initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => { + cy.get('[data-cy="MeasurementTools-split-button-secondary"]').click(); cy.get('[data-cy="Angle"]').click(); - cy.addAngle('.viewport-element', initPos, midPos, finalPos); + + cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); } ); @@ -392,31 +359,23 @@ Cypress.Commands.add('percyCanvasSnapshot', (name, options = {}) => { }); Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => { - cy.get('[data-cy="layout"]').click(); + cy.get('[data-cy="Layout"]').click(); - cy.get('.layoutChooser') - .find('tr') - .eq(rows - 1) - .find('td') - .eq(columns - 1) - .click(); + cy.get(`[data-cy="Layout-${columns - 1}-${rows - 1}"]`).click(); - cy.wait(1000); + cy.wait(10); + cy.waitDicomImage(); }); function convertCanvas(documentClone) { - documentClone - .querySelectorAll('canvas') - .forEach(selector => canvasToImage(selector)); + documentClone.querySelectorAll('canvas').forEach(selector => canvasToImage(selector)); return documentClone; } function unconvertCanvas(documentClone) { // Remove previously generated images - documentClone - .querySelectorAll('[data-percy-image]') - .forEach(selector => selector.remove()); + documentClone.querySelectorAll('[data-percy-image]').forEach(selector => selector.remove()); // Restore canvas visibility documentClone.querySelectorAll('[data-percy-canvas]').forEach(selector => { selector.removeAttribute('data-percy-canvas'); @@ -426,9 +385,7 @@ function unconvertCanvas(documentClone) { function canvasToImage(selectorOrEl) { let canvas = - typeof selectorOrEl === 'object' - ? selectorOrEl - : document.querySelector(selectorOrEl); + typeof selectorOrEl === 'object' ? selectorOrEl : document.querySelector(selectorOrEl); let image = document.createElement('img'); let canvasImageBase64 = canvas.toDataURL('image/png'); @@ -456,10 +413,7 @@ Cypress.Commands.add('openPreferences', () => { .scrollIntoView() .click() .then(() => { - cy.get('[data-cy="options-dropdown"]') - .last() - .click() - .wait(200); + cy.get('[data-cy="options-dropdown"]').last().click().wait(200); }); } }); @@ -494,9 +448,7 @@ Cypress.Commands.add('closePreferences', () => { cy.get('body').then(body => { // Close notification if displayed if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Close User Preferences Modal (if displayed) @@ -508,9 +460,11 @@ Cypress.Commands.add('closePreferences', () => { Cypress.Commands.add('selectPreferencesTab', tabAlias => { cy.initPreferencesModalAliases(); - cy.get(tabAlias) - .click() - .should('have.class', 'active'); + + cy.get(tabAlias).as('selectedTab'); + cy.get('@selectedTab').click(); + cy.get('@selectedTab').should('have.class', 'active'); + initPreferencesModalFooterBtnAliases(); }); @@ -526,9 +480,7 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Click on Save Button cy.get('@saveBtn').click(); @@ -547,28 +499,23 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Click on Save Button cy.get('@saveBtn').click(); }); }); -Cypress.Commands.add( - 'setNewHotkeyShortcutOnUserPreferencesModal', - (function_label, shortcut) => { - // Within scopes all `.get` and `.contains` to within the matched elements - // dom instead of checking from document - cy.get('.HotkeysPreferences').within(() => { - cy.contains(function_label) // label we're looking for - .parent() - .find('input') // closest input to that label - .type(shortcut, { force: true }); // Set new shortcut for that function - }); - } -); +Cypress.Commands.add('setNewHotkeyShortcutOnUserPreferencesModal', (function_label, shortcut) => { + // Within scopes all `.get` and `.contains` to within the matched elements + // dom instead of checking from document + cy.get('.HotkeysPreferences').within(() => { + cy.contains(function_label) // label we're looking for + .parent() + .find('input') // closest input to that label + .type(shortcut, { force: true }); // Set new shortcut for that function + }); +}); Cypress.Commands.add( 'setWindowLevelPreset', @@ -603,9 +550,7 @@ Cypress.Commands.add( Cypress.Commands.add('openDownloadImageModal', () => { // Click on More button - cy.get('[data-cy="Capture"]') - .as('captureBtn') - .click(); + cy.get('[data-cy="Capture"]').as('captureBtn').click(); }); Cypress.Commands.add('setLanguage', (language, save = true) => { @@ -622,16 +567,12 @@ Cypress.Commands.add('setLanguage', (language, save = true) => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } //Click on Save/Cancel button const toClick = save ? '@saveBtn' : '@cancelBtn'; - cy.get(toClick) - .scrollIntoView() - .click(); + cy.get(toClick).scrollIntoView().click(); }); }); @@ -639,7 +580,7 @@ Cypress.Commands.add('setLanguage', (language, save = true) => { // https://github.com/cypress-io/cypress/issues/7362 // uncomment this if you really need the network logs const origLog = Cypress.log; -Cypress.log = function(opts, ...other) { +Cypress.log = function (opts, ...other) { if (opts.displayName === 'script' || opts.name === 'request') { return; } diff --git a/platform/app/jestBabelTransform.js b/platform/app/jestBabelTransform.js index d1b645bc787..81c0a5cc963 100644 --- a/platform/app/jestBabelTransform.js +++ b/platform/app/jestBabelTransform.js @@ -1,5 +1,5 @@ -const babelJest = require("babel-jest"); +const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ - rootMode: "upward" + rootMode: 'upward', }); diff --git a/platform/app/netlify.toml b/platform/app/netlify.toml index 4af3dd5977b..bc07d914e5f 100644 --- a/platform/app/netlify.toml +++ b/platform/app/netlify.toml @@ -19,7 +19,7 @@ [build.environment] # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" - NODE_VERSION = "16.14.0" + NODE_VERSION = "18.16.1" YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" @@ -43,3 +43,5 @@ # COMMENT: For sharedArrayBuffer, see https://developer.chrome.com/blog/enabling-shared-array-buffer/ Cross-Origin-Embedder-Policy = "require-corp" Cross-Origin-Opener-Policy = "same-origin" + # set CORP to cross-origin for anyone who wants to use the viewer in an iframe + Cross-Origin-Resource-Policy = "cross-origin" diff --git a/platform/app/package.json b/platform/app/package.json index a95f7e0d3a0..d9a9f606001 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/app", - "version": "3.6.0", + "version": "3.7.0-beta.108", "productVersion": "3.4.0", "description": "OHIF Viewer", "author": "OHIF Contributors", @@ -26,11 +26,11 @@ "build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=false yarn run build", "build": "node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js", "dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.pwa.js", + "dev:no:cache": "cross-env NODE_ENV=development webpack serve --no-cache --config .webpack/webpack.pwa.js", "dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack serve --config .webpack/webpack.pwa.js", "dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js", "dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js", "dev:viewer": "yarn run dev", - "preinstall": "node preinstall.js", "start": "yarn run dev", "test:e2e": "cypress open", "test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'", @@ -51,23 +51,23 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-rt": "3.6.0", - "@ohif/extension-cornerstone-dicom-seg": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-microscopy": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-test": "3.6.0", - "@ohif/i18n": "3.6.0", - "@ohif/mode-basic-dev-mode": "3.6.0", - "@ohif/mode-longitudinal": "3.6.0", - "@ohif/mode-microscopy": "3.6.0", - "@ohif/mode-test": "3.6.0", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.20.3", + "@ohif/core": "3.7.0-beta.108", + "@ohif/extension-cornerstone": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.108", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.108", + "@ohif/extension-default": "3.7.0-beta.108", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.108", + "@ohif/extension-dicom-pdf": "3.7.0-beta.108", + "@ohif/extension-dicom-video": "3.7.0-beta.108", + "@ohif/extension-test": "3.7.0-beta.108", + "@ohif/i18n": "3.7.0-beta.108", + "@ohif/mode-basic-dev-mode": "3.7.0-beta.108", + "@ohif/mode-longitudinal": "3.7.0-beta.108", + "@ohif/mode-microscopy": "3.7.0-beta.108", + "@ohif/mode-test": "3.7.0-beta.108", + "@ohif/ui": "3.7.0-beta.108", "@types/react": "^17.0.38", "classnames": "^2.3.2", "core-js": "^3.16.1", @@ -95,7 +95,7 @@ "devDependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@percy/cypress": "^3.1.1", - "cypress": "^12.6.0", + "cypress": "^13.2.0", "cypress-file-upload": "^3.5.3", "glob": "^8.0.3", "identity-obj-proxy": "3.0.x", diff --git a/platform/app/pluginConfig.json b/platform/app/pluginConfig.json index 2f892db6c86..08a42deb0f2 100644 --- a/platform/app/pluginConfig.json +++ b/platform/app/pluginConfig.json @@ -57,6 +57,9 @@ { "packageName": "@ohif/mode-longitudinal" }, + { + "packageName": "@ohif/mode-segmentation" + }, { "packageName": "@ohif/mode-tmtv" }, diff --git a/platform/app/postcss.config.js b/platform/app/postcss.config.js index 6402436e4a6..a0daaed0810 100644 --- a/platform/app/postcss.config.js +++ b/platform/app/postcss.config.js @@ -1 +1 @@ -module.exports = require("../../postcss.config.js"); +module.exports = require('../../postcss.config.js'); diff --git a/platform/app/public/_redirects b/platform/app/public/_redirects index 9730bd0d3c5..c3f7726c347 100644 --- a/platform/app/public/_redirects +++ b/platform/app/public/_redirects @@ -1,6 +1,6 @@ # Specific to our deploy-preview # Our docs are published using CircleCI + GitBook -# Confgure redirects using netlify.toml +# Configure redirects using netlify.toml # Spa /* /index.html 200 diff --git a/platform/app/public/assets/yandex-browser-manifest.json b/platform/app/public/assets/yandex-browser-manifest.json index ce9b1c12ec3..846829c9948 100644 --- a/platform/app/public/assets/yandex-browser-manifest.json +++ b/platform/app/public/assets/yandex-browser-manifest.json @@ -6,4 +6,4 @@ "color": "#fff", "show_title": true } -} \ No newline at end of file +} diff --git a/platform/app/public/config/aws.js b/platform/app/public/config/aws.js index 35ffbde5ca6..f7dd9a06987 100644 --- a/platform/app/public/config/aws.js +++ b/platform/app/public/config/aws.js @@ -4,7 +4,7 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,38 +13,39 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', // Something here to check build wadoUriRoot: 'https://myserver.com/dicomweb', qidoRoot: 'https://myserver.com/dicomweb', wadoRoot: 'https://myserver.com/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: false, staticWado: true, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js index 8ff91875b8d..00013529ea8 100644 --- a/platform/app/public/config/default.js +++ b/platform/app/public/config/default.js @@ -3,15 +3,11 @@ window.config = { // whiteLabeling: {}, extensions: [], modes: [], - customizationService: { - // Shows a custom route -access via http://localhost:3000/custom - // helloPage: '@ohif/extension-default.customizationModule.helloPage', - }, + customizationService: {}, showStudyList: true, // some windows systems have issues with more than 3 web workers maxNumberOfWebWorkers: 3, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -37,21 +33,41 @@ window.config = { // }, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', - // old server - // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - - // new server wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', - + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video', + // whether the data source should use retrieveBulkData to grab metadata, + // and in case of relative path, what would it be relative to, options + // are in the series level or study level (some servers like series some study) + bulkDataURI: { + enabled: true, + relativeResolution: 'studies', + }, + omitQuotationForMultipartRequest: true, + }, + }, + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb2', + configuration: { + friendlyName: 'AWS S3 Static wado secondary server', + name: 'aws', + wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, supportsReject: false, imageRendering: 'wadors', @@ -68,29 +84,31 @@ window.config = { enabled: true, relativeResolution: 'studies', }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicomweb delegating proxy', namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy', sourceName: 'dicomwebproxy', configuration: { + friendlyName: 'dicomweb delegating proxy', name: 'dicomwebproxy', }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/default_16bit.js b/platform/app/public/config/default_16bit.js new file mode 100644 index 00000000000..20b14d6b9b1 --- /dev/null +++ b/platform/app/public/config/default_16bit.js @@ -0,0 +1,189 @@ +window.config = { + routerBasename: '/', + // whiteLabeling: {}, + extensions: [], + modes: [], + customizationService: { + // Shows a custom route -access via http://localhost:3000/custom + // helloPage: '@ohif/extension-default.customizationModule.helloPage', + }, + showStudyList: true, + // some windows systems have issues with more than 3 web workers + maxNumberOfWebWorkers: 3, + // below flag is for performance reasons, but it might not work for all servers + omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: false, + showCPUFallbackMessage: true, + showLoadingIndicator: true, + use16BitDataType: true, + useSharedArrayBuffer: 'AUTO', + maxNumRequests: { + interaction: 100, + thumbnail: 75, + // Prefetch number is dependent on the http protocol. For http 2 or + // above, the number of requests can be go a lot higher. + prefetch: 25, + }, + // filterQueryParam: false, + dataSources: [ + { + friendlyName: 'dcmjs DICOMWeb Server', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'aws', + // old server + // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + // new server + wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + qidoSupportsIncludeField: false, + supportsReject: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video,pdf', + }, + }, + { + friendlyName: 'dicom json', + namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', + sourceName: 'dicomjson', + configuration: { + name: 'json', + }, + }, + { + friendlyName: 'dicom local', + namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', + sourceName: 'dicomlocal', + configuration: {}, + }, + ], + httpErrorHandler: error => { + // This is 429 when rejected from the public idc sandbox too often. + console.warn(error.status); + + // Could use services manager here to bring up a dialog/modal if needed. + console.warn('test, navigate to https://ohif.org/'); + }, + // whiteLabeling: { + // /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */ + // createLogoComponentFn: function (React) { + // return React.createElement( + // 'a', + // { + // target: '_self', + // rel: 'noopener noreferrer', + // className: 'text-purple-600 line-through', + // href: '/', + // }, + // React.createElement('img', + // { + // src: './customLogo.svg', + // className: 'w-8 h-8', + // } + // )) + // }, + // }, + defaultDataSourceName: 'dicomweb', + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportVertical', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + { commandName: 'nextImage', label: 'Next Image', keys: ['down'] }, + { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] }, + // { + // commandName: 'previousViewportDisplaySet', + // label: 'Previous Series', + // keys: ['pagedown'], + // }, + // { + // commandName: 'nextViewportDisplaySet', + // label: 'Next Series', + // keys: ['pageup'], + // }, + { + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + label: 'Zoom', + keys: ['z'], + }, + // ~ Window level presets + { + commandName: 'windowLevelPreset1', + label: 'W/L Preset 1', + keys: ['1'], + }, + { + commandName: 'windowLevelPreset2', + label: 'W/L Preset 2', + keys: ['2'], + }, + { + commandName: 'windowLevelPreset3', + label: 'W/L Preset 3', + keys: ['3'], + }, + { + commandName: 'windowLevelPreset4', + label: 'W/L Preset 4', + keys: ['4'], + }, + { + commandName: 'windowLevelPreset5', + label: 'W/L Preset 5', + keys: ['5'], + }, + { + commandName: 'windowLevelPreset6', + label: 'W/L Preset 6', + keys: ['6'], + }, + { + commandName: 'windowLevelPreset7', + label: 'W/L Preset 7', + keys: ['7'], + }, + { + commandName: 'windowLevelPreset8', + label: 'W/L Preset 8', + keys: ['8'], + }, + { + commandName: 'windowLevelPreset9', + label: 'W/L Preset 9', + keys: ['9'], + }, + ], +}; diff --git a/platform/app/public/config/demo.js b/platform/app/public/config/demo.js index b4d43b86195..5dbd96ab156 100644 --- a/platform/app/public/config/demo.js +++ b/platform/app/public/config/demo.js @@ -4,17 +4,16 @@ window.config = { extensions: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, strictZSpacingForVolumeViewport: true, showCPUFallbackMessage: true, defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'DCM4CHEE Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'DCM4CHEE Server', name: 'DCM4CHEE', wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', @@ -25,6 +24,7 @@ window.config = { bulkDataURI: { enabled: false, }, + omitQuotationForMultipartRequest: true, }, }, ], diff --git a/platform/app/public/config/dicomweb-server.js b/platform/app/public/config/dicomweb-server.js index 4db99041765..a3522e587c7 100644 --- a/platform/app/public/config/dicomweb-server.js +++ b/platform/app/public/config/dicomweb-server.js @@ -4,7 +4,6 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,10 +12,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost:5985', qidoRoot: 'http://localhost:5985', @@ -28,21 +27,23 @@ window.config = { enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: false, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/dicomweb_relative.js b/platform/app/public/config/dicomweb_relative.js index af024abd70f..bfa51c34aec 100644 --- a/platform/app/public/config/dicomweb_relative.js +++ b/platform/app/public/config/dicomweb_relative.js @@ -5,7 +5,6 @@ window.config = { showStudyList: true, maxNumberOfWebWorkers: 3, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -14,16 +13,15 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', wadoUriRoot: '/dicomweb', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -31,21 +29,23 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/docker_nginx-orthanc.js b/platform/app/public/config/docker_nginx-orthanc.js index 60d7f083ce4..97316bc8adf 100644 --- a/platform/app/public/config/docker_nginx-orthanc.js +++ b/platform/app/public/config/docker_nginx-orthanc.js @@ -4,7 +4,6 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -12,10 +11,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Orthanc Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', wadoUriRoot: '/wado', qidoRoot: '/dicom-web', @@ -23,21 +22,23 @@ window.config = { qidoSupportsIncludeField: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/docker_openresty-orthanc-keycloak.js b/platform/app/public/config/docker_openresty-orthanc-keycloak.js index 07709a7ebef..5e9f7854f3b 100644 --- a/platform/app/public/config/docker_openresty-orthanc-keycloak.js +++ b/platform/app/public/config/docker_openresty-orthanc-keycloak.js @@ -1,32 +1,39 @@ window.config = { routerBasename: '/', showStudyList: true, + extensions: [], + modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, strictZSpacingForVolumeViewport: true, - servers: { - // This is an array, but we'll only use the first entry for now - dicomWeb: [ - { + defaultDataSourceName: 'dicomweb', + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', - wadoUriRoot: 'http://127.0.0.1/pacs/wado', + wadoUriRoot: 'http://127.0.0.1/pacs/dicom-web', qidoRoot: 'http://127.0.0.1/pacs/dicom-web', wadoRoot: 'http://127.0.0.1/pacs/dicom-web', - qidoSupportsIncludeField: false, + qidoSupportsIncludeField: true, + supportsReject: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', - // REQUIRED TAG: - // TODO: Remove tag after https://github.com/OHIF/ohif-core/pull/19 is merged and we bump version - // requestOptions: { - // undefined to use JWT + Bearer auth - // auth: 'orthanc:orthanc', - // }, + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + dicomUploadEnabled: true, + bulkDataURI: { + enabled: true, + }, }, - ], - }, + }, + ], // This is an array, but we'll only use the first entry for now oidc: [ { diff --git a/platform/app/public/config/docker_openresty-orthanc.js b/platform/app/public/config/docker_openresty-orthanc.js index 33e41387792..2500760bf22 100644 --- a/platform/app/public/config/docker_openresty-orthanc.js +++ b/platform/app/public/config/docker_openresty-orthanc.js @@ -4,7 +4,7 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -12,32 +12,41 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Orthanc Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', - wadoUriRoot: 'http://127.0.0.1/pacs/wado', + wadoUriRoot: 'http://127.0.0.1/pacs/dicom-web', qidoRoot: 'http://127.0.0.1/pacs/dicom-web', wadoRoot: 'http://127.0.0.1/pacs/dicom-web', - qidoSupportsIncludeField: false, + qidoSupportsIncludeField: true, + supportsReject: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + dicomUploadEnabled: true, + bulkDataURI: { + enabled: true, + }, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/e2e.js b/platform/app/public/config/e2e.js index 47903374cb2..bf095af71d1 100644 --- a/platform/app/public/config/e2e.js +++ b/platform/app/public/config/e2e.js @@ -4,7 +4,6 @@ window.config = { modes: ['@ohif/mode-test'], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, maxNumberOfWebWorkers: 3, showWarningMessageForCrossOrigin: false, showCPUFallbackMessage: false, @@ -13,10 +12,10 @@ window.config = { defaultDataSourceName: 'e2e', dataSources: [ { - friendlyName: 'StaticWado test data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'e2e', configuration: { + friendlyName: 'StaticWado test data', // The most important field to set for static WADO staticWado: true, name: 'StaticWADO', @@ -24,23 +23,43 @@ window.config = { qidoRoot: '/viewer-testdata', wadoRoot: '/viewer-testdata', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: true, singlepart: 'video,thumbnail,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'local', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', - qidoRoot: '/dicomweb', - wadoRoot: '/dicomweb', + qidoRoot: 'http://localhost:5000/dicomweb', + wadoRoot: 'http://localhost:5000/dicomweb', + qidoSupportsIncludeField: false, + supportsReject: true, + supportsStow: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video,pdf', + }, + }, + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'docker', + configuration: { + friendlyName: 'Static WADO Docker Data', + name: 'DCM4CHEE', + qidoRoot: 'http://localhost:25080/dicomweb', + wadoRoot: 'http://localhost:25080/dicomweb', qidoSupportsIncludeField: false, supportsReject: true, supportsStow: true, @@ -54,21 +73,15 @@ window.config = { }, }, { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'ohif', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', - // old server - // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // new server wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -98,18 +111,19 @@ window.config = { // }, // }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/google.js b/platform/app/public/config/google.js index 847e08ebbd0..b380a21a72f 100644 --- a/platform/app/public/config/google.js +++ b/platform/app/public/config/google.js @@ -6,7 +6,6 @@ window.config = { }, enableGoogleCloudAdapter: false, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,8 +16,7 @@ window.config = { // ~ REQUIRED // Authorization Server URL authority: 'https://accounts.google.com', - client_id: - '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', redirect_uri: '/callback', response_type: 'id_token token', scope: @@ -37,10 +35,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'GCP', wadoUriRoot: 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', @@ -55,21 +53,24 @@ window.config = { supportsFuzzyMatching: true, supportsWildcard: false, dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + configurationAPI: 'ohif.dataSourceConfigurationAPI.google', }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/idc.js b/platform/app/public/config/idc.js index 6056962bd75..0089182ee98 100644 --- a/platform/app/public/config/idc.js +++ b/platform/app/public/config/idc.js @@ -2,7 +2,6 @@ window.config = { routerBasename: '/', enableGoogleCloudAdapter: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,8 +16,7 @@ window.config = { // ~ REQUIRED // Authorization Server URL authority: 'https://accounts.google.com', - client_id: - '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', redirect_uri: '/callback', // `OHIFStandaloneViewer.js` response_type: 'id_token token', scope: diff --git a/platform/app/public/config/local_dcm4chee.js b/platform/app/public/config/local_dcm4chee.js index 763604ffdfd..a521bfef060 100644 --- a/platform/app/public/config/local_dcm4chee.js +++ b/platform/app/public/config/local_dcm4chee.js @@ -8,7 +8,6 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -16,10 +15,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'DCM4CHEE Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'DCM4CHEE Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -39,21 +38,23 @@ window.config = { bulkDataURI: { enabled: true, }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], studyListFunctionsEnabled: true, diff --git a/platform/app/public/config/local_orthanc.js b/platform/app/public/config/local_orthanc.js index 3153408b08e..eabb22f21a0 100644 --- a/platform/app/public/config/local_orthanc.js +++ b/platform/app/public/config/local_orthanc.js @@ -16,10 +16,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'local Orthanc DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost/dicom-web', qidoRoot: 'http://localhost/dicom-web', @@ -38,18 +38,19 @@ window.config = { }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/local_static.js b/platform/app/public/config/local_static.js index 6774e1b9a08..89f205d0115 100644 --- a/platform/app/public/config/local_static.js +++ b/platform/app/public/config/local_static.js @@ -1,14 +1,11 @@ window.config = { routerBasename: '/', - customizationService: [ - '@ohif/extension-default.customizationModule.helloPage', - ], + customizationService: ['@ohif/extension-default.customizationModule.helloPage'], extensions: [], modes: [], showStudyList: true, maxNumberOfWebWorkers: 4, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,15 +14,14 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -33,21 +29,23 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/multiple.js b/platform/app/public/config/multiple.js index 07a28426444..57684a9ef50 100644 --- a/platform/app/public/config/multiple.js +++ b/platform/app/public/config/multiple.js @@ -16,7 +16,6 @@ window.config = { showStudyList: true, maxNumberOfWebWorkers: 4, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -25,10 +24,10 @@ window.config = { defaultDataSourceName: 'default', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'default', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', @@ -42,13 +41,14 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'ohif', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'aws', // old server // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', @@ -59,7 +59,6 @@ window.config = { qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -70,15 +69,14 @@ window.config = { }, }, { - friendlyName: 'AWS S3 OHIF', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'aws', configuration: { + friendlyName: 'AWS S3 OHIF', name: 'aws', qidoRoot: 'https://dd32w2rfebxel.cloudfront.net/dicomweb', wadoRoot: 'https://dd32w2rfebxel.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, supportsStow: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', @@ -90,16 +88,15 @@ window.config = { }, }, { - friendlyName: 'E2E Test Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'e2e', configuration: { + friendlyName: 'E2E Test Data', name: 'DCM4CHEE', wadoUriRoot: '/viewer-testdata', qidoRoot: '/viewer-testdata', wadoRoot: '/viewer-testdata', qidoSupportsIncludeField: false, - supportsReject: false, supportsStow: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', @@ -111,18 +108,19 @@ window.config = { }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/netlify.js b/platform/app/public/config/netlify.js index 1bc23c049d9..a729d1c8d50 100644 --- a/platform/app/public/config/netlify.js +++ b/platform/app/public/config/netlify.js @@ -4,7 +4,6 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,15 +12,42 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video', + // whether the data source should use retrieveBulkData to grab metadata, + // and in case of relative path, what would it be relative to, options + // are in the series level or study level (some servers like series some study) + bulkDataURI: { + enabled: true, + relativeResolution: 'studies', + }, + omitQuotationForMultipartRequest: true, + }, + }, + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb2', + configuration: { + friendlyName: 'AWS S3 Static wado secondary server', + name: 'aws', + wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, supportsReject: false, imageRendering: 'wadors', @@ -38,21 +64,23 @@ window.config = { enabled: true, relativeResolution: 'studies', }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/public_dicomweb.js b/platform/app/public/config/public_dicomweb.js index 322d8968465..2dc7503744f 100644 --- a/platform/app/public/config/public_dicomweb.js +++ b/platform/app/public/config/public_dicomweb.js @@ -2,7 +2,6 @@ window.config = { routerBasename: '/', showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -18,6 +17,7 @@ window.config = { imageRendering: 'wadors', thumbnailRendering: 'wadors', supportsFuzzyMatching: true, + omitQuotationForMultipartRequest: true, }, ], }, diff --git a/platform/app/public/es6-shim.min.js b/platform/app/public/es6-shim.min.js index f8ef71c73a7..c1447d6fc70 100644 --- a/platform/app/public/es6-shim.min.js +++ b/platform/app/public/es6-shim.min.js @@ -7,6 +7,3573 @@ * Details and documentation: * https://github.com/paulmillr/es6-shim/ */ -(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else if(typeof exports==="object"){module.exports=t()}else{e.returnExports=t()}})(this,function(){"use strict";var e=Function.call.bind(Function.apply);var t=Function.call.bind(Function.call);var r=Array.isArray;var n=Object.keys;var o=function notThunker(t){return function notThunk(){return!e(t,this,arguments)}};var i=function(e){try{e();return false}catch(t){return true}};var a=function valueOrFalseIfThrows(e){try{return e()}catch(t){return false}};var u=o(i);var f=function(){return!i(function(){return Object.defineProperty({},"x",{get:function(){}})})};var s=!!Object.defineProperty&&f();var c=function foo(){}.name==="foo";var l=Function.call.bind(Array.prototype.forEach);var p=Function.call.bind(Array.prototype.reduce);var v=Function.call.bind(Array.prototype.filter);var y=Function.call.bind(Array.prototype.some);var h=function(e,t,r,n){if(!n&&t in e){return}if(s){Object.defineProperty(e,t,{configurable:true,enumerable:false,writable:true,value:r})}else{e[t]=r}};var b=function(e,t,r){l(n(t),function(n){var o=t[n];h(e,n,o,!!r)})};var g=Function.call.bind(Object.prototype.toString);var d=typeof/abc/==="function"?function IsCallableSlow(e){return typeof e==="function"&&g(e)==="[object Function]"}:function IsCallableFast(e){return typeof e==="function"};var m={getter:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}Object.defineProperty(e,t,{configurable:true,enumerable:false,get:r})},proxy:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,{configurable:n.configurable,enumerable:n.enumerable,get:function getKey(){return e[t]},set:function setKey(r){e[t]=r}})},redefine:function(e,t,r){if(s){var n=Object.getOwnPropertyDescriptor(e,t);n.value=r;Object.defineProperty(e,t,n)}else{e[t]=r}},defineByDescriptor:function(e,t,r){if(s){Object.defineProperty(e,t,r)}else if("value"in r){e[t]=r.value}},preserveToString:function(e,t){if(t&&d(t.toString)){h(e,"toString",t.toString.bind(t),true)}}};var O=Object.create||function(e,t){var r=function Prototype(){};r.prototype=e;var o=new r;if(typeof t!=="undefined"){n(t).forEach(function(e){m.defineByDescriptor(o,e,t[e])})}return o};var w=function(e,t){if(!Object.setPrototypeOf){return false}return a(function(){var r=function Subclass(t){var r=new e(t);Object.setPrototypeOf(r,Subclass.prototype);return r};Object.setPrototypeOf(r,e);r.prototype=O(e.prototype,{constructor:{value:r}});return t(r)})};var j=function(){if(typeof self!=="undefined"){return self}if(typeof window!=="undefined"){return window}if(typeof global!=="undefined"){return global}throw new Error("unable to locate global object")};var S=j();var T=S.isFinite;var I=Function.call.bind(String.prototype.indexOf);var E=Function.apply.bind(Array.prototype.indexOf);var P=Function.call.bind(Array.prototype.concat);var C=Function.call.bind(String.prototype.slice);var M=Function.call.bind(Array.prototype.push);var x=Function.apply.bind(Array.prototype.push);var N=Function.call.bind(Array.prototype.shift);var A=Math.max;var R=Math.min;var _=Math.floor;var k=Math.abs;var L=Math.exp;var F=Math.log;var D=Math.sqrt;var z=Function.call.bind(Object.prototype.hasOwnProperty);var q;var W=function(){};var G=S.Map;var H=G&&G.prototype["delete"];var V=G&&G.prototype.get;var B=G&&G.prototype.has;var U=G&&G.prototype.set;var $=S.Symbol||{};var J=$.species||"@@species";var X=Number.isNaN||function isNaN(e){return e!==e};var K=Number.isFinite||function isFinite(e){return typeof e==="number"&&T(e)};var Z=d(Math.sign)?Math.sign:function sign(e){var t=Number(e);if(t===0){return t}if(X(t)){return t}return t<0?-1:1};var Y=function log1p(e){var t=Number(e);if(t<-1||X(t)){return NaN}if(t===0||t===Infinity){return t}if(t===-1){return-Infinity}return 1+t-1===0?t:t*(F(1+t)/(1+t-1))};var Q=function isArguments(e){return g(e)==="[object Arguments]"};var ee=function isArguments(e){return e!==null&&typeof e==="object"&&typeof e.length==="number"&&e.length>=0&&g(e)!=="[object Array]"&&g(e.callee)==="[object Function]"};var te=Q(arguments)?Q:ee;var re={primitive:function(e){return e===null||typeof e!=="function"&&typeof e!=="object"},string:function(e){return g(e)==="[object String]"},regex:function(e){return g(e)==="[object RegExp]"},symbol:function(e){return typeof S.Symbol==="function"&&typeof e==="symbol"}};var ne=function overrideNative(e,t,r){var n=e[t];h(e,t,r,true);m.preserveToString(e[t],n)};var oe=typeof $==="function"&&typeof $["for"]==="function"&&re.symbol($());var ie=re.symbol($.iterator)?$.iterator:"_es6-shim iterator_";if(S.Set&&typeof(new S.Set)["@@iterator"]==="function"){ie="@@iterator"}if(!S.Reflect){h(S,"Reflect",{},true)}var ae=S.Reflect;var ue=String;var fe=typeof document==="undefined"||!document?null:document.all;var se=fe==null?function isNullOrUndefined(e){return e==null}:function isNullOrUndefinedAndNotDocumentAll(e){return e==null&&e!==fe};var ce={Call:function Call(t,r){var n=arguments.length>2?arguments[2]:[];if(!ce.IsCallable(t)){throw new TypeError(t+" is not a function")}return e(t,r,n)},RequireObjectCoercible:function(e,t){if(se(e)){throw new TypeError(t||"Cannot call method on "+e)}return e},TypeIsObject:function(e){if(e===void 0||e===null||e===true||e===false){return false}return typeof e==="function"||typeof e==="object"||e===fe},ToObject:function(e,t){return Object(ce.RequireObjectCoercible(e,t))},IsCallable:d,IsConstructor:function(e){return ce.IsCallable(e)},ToInt32:function(e){return ce.ToNumber(e)>>0},ToUint32:function(e){return ce.ToNumber(e)>>>0},ToNumber:function(e){if(g(e)==="[object Symbol]"){throw new TypeError("Cannot convert a Symbol value to a number")}return+e},ToInteger:function(e){var t=ce.ToNumber(e);if(X(t)){return 0}if(t===0||!K(t)){return t}return(t>0?1:-1)*_(k(t))},ToLength:function(e){var t=ce.ToInteger(e);if(t<=0){return 0}if(t>Number.MAX_SAFE_INTEGER){return Number.MAX_SAFE_INTEGER}return t},SameValue:function(e,t){if(e===t){if(e===0){return 1/e===1/t}return true}return X(e)&&X(t)},SameValueZero:function(e,t){return e===t||X(e)&&X(t)},IsIterable:function(e){return ce.TypeIsObject(e)&&(typeof e[ie]!=="undefined"||te(e))},GetIterator:function(e){if(te(e)){return new q(e,"value")}var t=ce.GetMethod(e,ie);if(!ce.IsCallable(t)){throw new TypeError("value is not an iterable")}var r=ce.Call(t,e);if(!ce.TypeIsObject(r)){throw new TypeError("bad iterator")}return r},GetMethod:function(e,t){var r=ce.ToObject(e)[t];if(se(r)){return void 0}if(!ce.IsCallable(r)){throw new TypeError("Method not callable: "+t)}return r},IteratorComplete:function(e){return!!e.done},IteratorClose:function(e,t){var r=ce.GetMethod(e,"return");if(r===void 0){return}var n,o;try{n=ce.Call(r,e)}catch(i){o=i}if(t){return}if(o){throw o}if(!ce.TypeIsObject(n)){throw new TypeError("Iterator's return method returned a non-object.")}},IteratorNext:function(e){var t=arguments.length>1?e.next(arguments[1]):e.next();if(!ce.TypeIsObject(t)){throw new TypeError("bad iterator")}return t},IteratorStep:function(e){var t=ce.IteratorNext(e);var r=ce.IteratorComplete(t);return r?false:t},Construct:function(e,t,r,n){var o=typeof r==="undefined"?e:r;if(!n&&ae.construct){return ae.construct(e,t,o)}var i=o.prototype;if(!ce.TypeIsObject(i)){i=Object.prototype}var a=O(i);var u=ce.Call(e,a,t);return ce.TypeIsObject(u)?u:a},SpeciesConstructor:function(e,t){var r=e.constructor;if(r===void 0){return t}if(!ce.TypeIsObject(r)){throw new TypeError("Bad constructor")}var n=r[J];if(se(n)){return t}if(!ce.IsConstructor(n)){throw new TypeError("Bad @@species")}return n},CreateHTML:function(e,t,r,n){var o=ce.ToString(e);var i="<"+t;if(r!==""){var a=ce.ToString(n);var u=a.replace(/"/g,""");i+=" "+r+'="'+u+'"'}var f=i+">";var s=f+o;return s+""},IsRegExp:function IsRegExp(e){if(!ce.TypeIsObject(e)){return false}var t=e[$.match];if(typeof t!=="undefined"){return!!t}return re.regex(e)},ToString:function ToString(e){return ue(e)}};if(s&&oe){var le=function defineWellKnownSymbol(e){if(re.symbol($[e])){return $[e]}var t=$["for"]("Symbol."+e);Object.defineProperty($,e,{configurable:false,enumerable:false,writable:false,value:t});return t};if(!re.symbol($.search)){var pe=le("search");var ve=String.prototype.search;h(RegExp.prototype,pe,function search(e){return ce.Call(ve,e,[this])});var ye=function search(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,pe);if(typeof r!=="undefined"){return ce.Call(r,e,[t])}}return ce.Call(ve,t,[ce.ToString(e)])};ne(String.prototype,"search",ye)}if(!re.symbol($.replace)){var he=le("replace");var be=String.prototype.replace;h(RegExp.prototype,he,function replace(e,t){return ce.Call(be,e,[this,t])});var ge=function replace(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,he);if(typeof n!=="undefined"){return ce.Call(n,e,[r,t])}}return ce.Call(be,r,[ce.ToString(e),t])};ne(String.prototype,"replace",ge)}if(!re.symbol($.split)){var de=le("split");var me=String.prototype.split;h(RegExp.prototype,de,function split(e,t){return ce.Call(me,e,[this,t])});var Oe=function split(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,de);if(typeof n!=="undefined"){return ce.Call(n,e,[r,t])}}return ce.Call(me,r,[ce.ToString(e),t])};ne(String.prototype,"split",Oe)}var we=re.symbol($.match);var je=we&&function(){var e={};e[$.match]=function(){return 42};return"a".match(e)!==42}();if(!we||je){var Se=le("match");var Te=String.prototype.match;h(RegExp.prototype,Se,function match(e){return ce.Call(Te,e,[this])});var Ie=function match(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,Se);if(typeof r!=="undefined"){return ce.Call(r,e,[t])}}return ce.Call(Te,t,[ce.ToString(e)])};ne(String.prototype,"match",Ie)}}var Ee=function wrapConstructor(e,t,r){m.preserveToString(t,e);if(Object.setPrototypeOf){Object.setPrototypeOf(e,t)}if(s){l(Object.getOwnPropertyNames(e),function(n){if(n in W||r[n]){return}m.proxy(e,n,t)})}else{l(Object.keys(e),function(n){if(n in W||r[n]){return}t[n]=e[n]})}t.prototype=e.prototype;m.redefine(e.prototype,"constructor",t)};var Pe=function(){return this};var Ce=function(e){if(s&&!z(e,J)){m.getter(e,J,Pe)}};var Me=function(e,t){var r=t||function iterator(){return this};h(e,ie,r);if(!e[ie]&&re.symbol(ie)){e[ie]=r}};var xe=function createDataProperty(e,t,r){if(s){Object.defineProperty(e,t,{configurable:true,enumerable:true,writable:true,value:r})}else{e[t]=r}};var Ne=function createDataPropertyOrThrow(e,t,r){xe(e,t,r);if(!ce.SameValue(e[t],r)){throw new TypeError("property is nonconfigurable")}};var Ae=function(e,t,r,n){if(!ce.TypeIsObject(e)){throw new TypeError("Constructor requires `new`: "+t.name)}var o=t.prototype;if(!ce.TypeIsObject(o)){o=r}var i=O(o);for(var a in n){if(z(n,a)){var u=n[a];h(i,a,u,true)}}return i};if(String.fromCodePoint&&String.fromCodePoint.length!==1){var Re=String.fromCodePoint;ne(String,"fromCodePoint",function fromCodePoint(e){return ce.Call(Re,this,arguments)})}var _e={fromCodePoint:function fromCodePoint(e){var t=[];var r;for(var n=0,o=arguments.length;n1114111){throw new RangeError("Invalid code point "+r)}if(r<65536){M(t,String.fromCharCode(r))}else{r-=65536;M(t,String.fromCharCode((r>>10)+55296));M(t,String.fromCharCode(r%1024+56320))}}return t.join("")},raw:function raw(e){var t=ce.ToObject(e,"bad callSite");var r=ce.ToObject(t.raw,"bad raw value");var n=r.length;var o=ce.ToLength(n);if(o<=0){return""}var i=[];var a=0;var u,f,s,c;while(a=o){break}f=a+1=Le){throw new RangeError("repeat count must be less than infinity and not overflow maximum string size")}return ke(t,r)},startsWith:function startsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method "startsWith" with a regex')}var r=ce.ToString(e);var n;if(arguments.length>1){n=arguments[1]}var o=A(ce.ToInteger(n),0);return C(t,o,o+r.length)===r},endsWith:function endsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method "endsWith" with a regex')}var r=ce.ToString(e);var n=t.length;var o;if(arguments.length>1){o=arguments[1]}var i=typeof o==="undefined"?n:ce.ToInteger(o);var a=R(A(i,0),n);return C(t,a-r.length,a)===r},includes:function includes(e){if(ce.IsRegExp(e)){throw new TypeError('"includes" does not accept a RegExp')}var t=ce.ToString(e);var r;if(arguments.length>1){r=arguments[1]}return I(this,t,r)!==-1},codePointAt:function codePointAt(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);var n=t.length;if(r>=0&&r56319||i){return o}var a=t.charCodeAt(r+1);if(a<56320||a>57343){return o}return(o-55296)*1024+(a-56320)+65536}}};if(String.prototype.includes&&"a".includes("a",Infinity)!==false){ne(String.prototype,"includes",Fe.includes)}if(String.prototype.startsWith&&String.prototype.endsWith){var De=i(function(){return"/a/".startsWith(/a/)});var ze=a(function(){return"abc".startsWith("a",Infinity)===false});if(!De||!ze){ne(String.prototype,"startsWith",Fe.startsWith);ne(String.prototype,"endsWith",Fe.endsWith)}}if(oe){var qe=a(function(){var e=/a/;e[$.match]=false;return"/a/".startsWith(e)});if(!qe){ne(String.prototype,"startsWith",Fe.startsWith)}var We=a(function(){var e=/a/;e[$.match]=false;return"/a/".endsWith(e)});if(!We){ne(String.prototype,"endsWith",Fe.endsWith)}var Ge=a(function(){var e=/a/;e[$.match]=false;return"/a/".includes(e)});if(!Ge){ne(String.prototype,"includes",Fe.includes)}}b(String.prototype,Fe);var He=["\t\n\x0B\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003","\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028","\u2029\ufeff"].join("");var Ve=new RegExp("(^["+He+"]+)|(["+He+"]+$)","g");var Be=function trim(){return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve,"")};var Ue=["\x85","\u200b","\ufffe"].join("");var $e=new RegExp("["+Ue+"]","g");var Je=/^[-+]0x[0-9a-f]+$/i;var Xe=Ue.trim().length!==Ue.length;h(String.prototype,"trim",Be,Xe);var Ke=function(e){return{value:e,done:arguments.length===0}};var Ze=function(e){ce.RequireObjectCoercible(e);this._s=ce.ToString(e);this._i=0};Ze.prototype.next=function(){var e=this._s;var t=this._i;if(typeof e==="undefined"||t>=e.length){this._s=void 0;return Ke()}var r=e.charCodeAt(t);var n,o;if(r<55296||r>56319||t+1===e.length){o=1}else{n=e.charCodeAt(t+1);o=n<56320||n>57343?1:2}this._i=t+o;return Ke(e.substr(t,o))};Me(Ze.prototype);Me(String.prototype,function(){return new Ze(this)});var Ye={from:function from(e){var r=this;var n;if(arguments.length>1){n=arguments[1]}var o,i;if(typeof n==="undefined"){o=false}else{if(!ce.IsCallable(n)){throw new TypeError("Array.from: when provided, the second argument must be a function")}if(arguments.length>2){i=arguments[2]}o=true}var a=typeof(te(e)||ce.GetMethod(e,ie))!=="undefined";var u,f,s;if(a){f=ce.IsConstructor(r)?Object(new r):[];var c=ce.GetIterator(e);var l,p;s=0;while(true){l=ce.IteratorStep(c);if(l===false){break}p=l.value;try{if(o){p=typeof i==="undefined"?n(p,s):t(n,i,p,s)}f[s]=p}catch(v){ce.IteratorClose(c,true);throw v}s+=1}u=s}else{var y=ce.ToObject(e);u=ce.ToLength(y.length);f=ce.IsConstructor(r)?Object(new r(u)):new Array(u);var h;for(s=0;s2){f=arguments[2]}var s=typeof f==="undefined"?n:ce.ToInteger(f);var c=s<0?A(n+s,0):R(s,n);var l=R(c-u,n-a);var p=1;if(u0){if(u in r){r[a]=r[u]}else{delete r[a]}u+=p;a+=p;l-=1}return r},fill:function fill(e){var t;if(arguments.length>1){t=arguments[1]}var r;if(arguments.length>2){r=arguments[2]}var n=ce.ToObject(this);var o=ce.ToLength(n.length);t=ce.ToInteger(typeof t==="undefined"?0:t);r=ce.ToInteger(typeof r==="undefined"?o:r);var i=t<0?A(o+t,0):R(t,o);var a=r<0?o+r:r;for(var u=i;u1?arguments[1]:null;for(var i=0,a;i1?arguments[1]:null;for(var i=0;i1&&typeof arguments[1]!=="undefined"){return ce.Call(it,this,arguments)}else{return t(it,this,e)}})}var at=-(Math.pow(2,32)-1);var ut=function(e,r){var n={length:at};n[r?(n.length>>>0)-1:0]=true;return a(function(){t(e,n,function(){throw new RangeError("should not reach here")},[]);return true})};if(!ut(Array.prototype.forEach)){var ft=Array.prototype.forEach;ne(Array.prototype,"forEach",function forEach(e){return ce.Call(ft,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.map)){var st=Array.prototype.map;ne(Array.prototype,"map",function map(e){return ce.Call(st,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.filter)){var ct=Array.prototype.filter;ne(Array.prototype,"filter",function filter(e){return ce.Call(ct,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.some)){var lt=Array.prototype.some;ne(Array.prototype,"some",function some(e){return ce.Call(lt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.every)){var pt=Array.prototype.every;ne(Array.prototype,"every",function every(e){return ce.Call(pt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduce)){var vt=Array.prototype.reduce;ne(Array.prototype,"reduce",function reduce(e){return ce.Call(vt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduceRight,true)){var yt=Array.prototype.reduceRight;ne(Array.prototype,"reduceRight",function reduceRight(e){return ce.Call(yt,this.length>=0?this:[],arguments)},true)}var ht=Number("0o10")!==8;var bt=Number("0b10")!==2;var gt=y(Ue,function(e){return Number(e+0+e)===0});if(ht||bt||gt){var dt=Number;var mt=/^0b[01]+$/i;var Ot=/^0o[0-7]+$/i;var wt=mt.test.bind(mt);var jt=Ot.test.bind(Ot);var St=function(e){var t;if(typeof e.valueOf==="function"){t=e.valueOf();if(re.primitive(t)){return t}}if(typeof e.toString==="function"){t=e.toString();if(re.primitive(t)){return t}}throw new TypeError("No default value")};var Tt=$e.test.bind($e);var It=Je.test.bind(Je);var Et=function(){var e=function Number(t){var r;if(arguments.length>0){r=re.primitive(t)?t:St(t,"number")}else{r=0}if(typeof r==="string"){r=ce.Call(Be,r);if(wt(r)){r=parseInt(C(r,2),2)}else if(jt(r)){r=parseInt(C(r,2),8)}else if(Tt(r)||It(r)){r=NaN}}var n=this;var o=a(function(){dt.prototype.valueOf.call(n);return true});if(n instanceof e&&!o){return new dt(r)}return dt(r)};return e}();Ee(dt,Et,{});b(Et,{NaN:dt.NaN,MAX_VALUE:dt.MAX_VALUE,MIN_VALUE:dt.MIN_VALUE,NEGATIVE_INFINITY:dt.NEGATIVE_INFINITY,POSITIVE_INFINITY:dt.POSITIVE_INFINITY});Number=Et;m.redefine(S,"Number",Et)}var Pt=Math.pow(2,53)-1;b(Number,{MAX_SAFE_INTEGER:Pt,MIN_SAFE_INTEGER:-Pt,EPSILON:2.220446049250313e-16,parseInt:S.parseInt,parseFloat:S.parseFloat,isFinite:K,isInteger:function isInteger(e){return K(e)&&ce.ToInteger(e)===e},isSafeInteger:function isSafeInteger(e){return Number.isInteger(e)&&k(e)<=Number.MAX_SAFE_INTEGER},isNaN:X});h(Number,"parseInt",S.parseInt,Number.parseInt!==S.parseInt);if([,1].find(function(){return true})===1){ne(Array.prototype,"find",et.find)}if([,1].findIndex(function(){return true})!==0){ne(Array.prototype,"findIndex",et.findIndex)}var Ct=Function.bind.call(Function.bind,Object.prototype.propertyIsEnumerable);var Mt=function ensureEnumerable(e,t){if(s&&Ct(e,t)){Object.defineProperty(e,t,{enumerable:false})}};var xt=function sliceArgs(){var e=Number(this);var t=arguments.length;var r=t-e;var n=new Array(r<0?0:r);for(var o=e;o1){return NaN}var r=k(t);return Z(t)*Y(2*r/(1-r))/2},cbrt:function cbrt(e){var t=Number(e);if(t===0){return t}var r=t<0;var n;if(r){t=-t}if(t===Infinity){n=Infinity}else{n=L(F(t)/3);n=(t/(n*n)+2*n)/3}return r?-n:n},clz32:function clz32(e){var t=Number(e);var r=ce.ToUint32(t);if(r===0){return 32}return Pr?ce.Call(Pr,r):31-_(F(r+.5)*Ir)},cosh:function cosh(e){var t=Number(e);if(t===0){return 1}if(X(t)){return NaN}if(!T(t)){return Infinity}var r=L(k(t)-1);return(r+1/(r*Tr*Tr))*(Tr/2)},expm1:function expm1(e){var t=Number(e);if(t===-Infinity){return-1}if(!T(t)||t===0){return t}if(k(t)>.5){return L(t)-1}var r=t;var n=0;var o=1;while(n+r!==n){n+=r;o+=1;r*=t/o}return n},hypot:function hypot(e,t){var r=0;var n=0;for(var o=0;o0?i/n*(i/n):i}}return n===Infinity?Infinity:n*D(r)},log2:function log2(e){return F(e)*Ir},log10:function log10(e){return F(e)*Er},log1p:Y,sign:Z,sinh:function sinh(e){var t=Number(e);if(!T(t)||t===0){return t}var r=k(t);if(r<1){var n=Math.expm1(r);return Z(t)*n*(1+1/(n+1))/2}var o=L(r-1);return Z(t)*(o-1/(o*Tr*Tr))*(Tr/2)},tanh:function tanh(e){var t=Number(e);if(X(t)||t===0){return t}if(t>=20){return 1}if(t<=-20){return-1}return(Math.expm1(t)-Math.expm1(-t))/(L(t)+L(-t))},trunc:function trunc(e){var t=Number(e);return t<0?-_(-t):_(t)},imul:function imul(e,t){var r=ce.ToUint32(e);var n=ce.ToUint32(t);var o=r>>>16&65535;var i=r&65535;var a=n>>>16&65535;var u=n&65535;return i*u+(o*u+i*a<<16>>>0)|0},fround:function fround(e){var t=Number(e);if(t===0||t===Infinity||t===-Infinity||X(t)){return t}var r=Z(t);var n=k(t);if(njr||X(i)){return r*Infinity}return r*i}};var Mr=function withinULPDistance(e,t,r){return k(1-e/t)/Number.EPSILON<(r||8)};b(Math,Cr);h(Math,"sinh",Cr.sinh,Math.sinh(710)===Infinity);h(Math,"cosh",Cr.cosh,Math.cosh(710)===Infinity);h(Math,"log1p",Cr.log1p,Math.log1p(-1e-17)!==-1e-17);h(Math,"asinh",Cr.asinh,Math.asinh(-1e7)!==-Math.asinh(1e7));h(Math,"asinh",Cr.asinh,Math.asinh(1e300)===Infinity);h(Math,"atanh",Cr.atanh,Math.atanh(1e-300)===0);h(Math,"tanh",Cr.tanh,Math.tanh(-2e-17)!==-2e-17); - h(Math,"acosh",Cr.acosh,Math.acosh(Number.MAX_VALUE)===Infinity);h(Math,"acosh",Cr.acosh,!Mr(Math.acosh(1+Number.EPSILON),Math.sqrt(2*Number.EPSILON)));h(Math,"cbrt",Cr.cbrt,!Mr(Math.cbrt(1e-300),1e-100));h(Math,"sinh",Cr.sinh,Math.sinh(-2e-17)!==-2e-17);var xr=Math.expm1(10);h(Math,"expm1",Cr.expm1,xr>22025.465794806718||xr<22025.465794806718);var Nr=Math.round;var Ar=Math.round(.5-Number.EPSILON/4)===0&&Math.round(-.5+Number.EPSILON/3.99)===1;var Rr=mr+1;var _r=2*mr-1;var kr=[Rr,_r].every(function(e){return Math.round(e)===e});h(Math,"round",function round(e){var t=_(e);var r=t===-1?-0:t+1;return e-t<.5?t:r},!Ar||!kr);m.preserveToString(Math.round,Nr);var Lr=Math.imul;if(Math.imul(4294967295,5)!==-5){Math.imul=Cr.imul;m.preserveToString(Math.imul,Lr)}if(Math.imul.length!==2){ne(Math,"imul",function imul(e,t){return ce.Call(Lr,Math,arguments)})}var Fr=function(){var e=S.setTimeout;if(typeof e!=="function"&&typeof e!=="object"){return}ce.IsPromise=function(e){if(!ce.TypeIsObject(e)){return false}if(typeof e._promise==="undefined"){return false}return true};var r=function(e){if(!ce.IsConstructor(e)){throw new TypeError("Bad promise constructor")}var t=this;var r=function(e,r){if(t.resolve!==void 0||t.reject!==void 0){throw new TypeError("Bad Promise implementation!")}t.resolve=e;t.reject=r};t.resolve=void 0;t.reject=void 0;t.promise=new e(r);if(!(ce.IsCallable(t.resolve)&&ce.IsCallable(t.reject))){throw new TypeError("Bad promise constructor")}};var n;if(typeof window!=="undefined"&&ce.IsCallable(window.postMessage)){n=function(){var e=[];var t="zero-timeout-message";var r=function(r){M(e,r);window.postMessage(t,"*")};var n=function(r){if(r.source===window&&r.data===t){r.stopPropagation();if(e.length===0){return}var n=N(e);n()}};window.addEventListener("message",n,true);return r}}var o=function(){var e=S.Promise;var t=e&&e.resolve&&e.resolve();return t&&function(e){return t.then(e)}};var i=ce.IsCallable(S.setImmediate)?S.setImmediate:typeof process==="object"&&process.nextTick?process.nextTick:o()||(ce.IsCallable(n)?n():function(t){e(t,0)});var a=function(e){return e};var u=function(e){throw e};var f=0;var s=1;var c=2;var l=0;var p=1;var v=2;var y={};var h=function(e,t,r){i(function(){g(e,t,r)})};var g=function(e,t,r){var n,o;if(t===y){return e(r)}try{n=e(r);o=t.resolve}catch(i){n=i;o=t.reject}o(n)};var d=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.fulfillReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o0){h(r.rejectReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o2&&arguments[2]===y;if(b&&o===E){i=y}else{i=new r(o)}var g=ce.IsCallable(e)?e:a;var d=ce.IsCallable(t)?t:u;var m=n._promise;var O;if(m.state===f){if(m.reactionLength===0){m.fulfillReactionHandler0=g;m.rejectReactionHandler0=d;m.reactionCapability0=i}else{var w=3*(m.reactionLength-1);m[w+l]=g;m[w+p]=d;m[w+v]=i}m.reactionLength+=1}else if(m.state===s){O=m.result;h(g,i,O)}else if(m.state===c){O=m.result;h(d,i,O)}else{throw new TypeError("unexpected Promise state")}return i.promise}});y=new r(E);I=T.then;return E}();if(S.Promise){delete S.Promise.accept;delete S.Promise.defer;delete S.Promise.prototype.chain}if(typeof Fr==="function"){b(S,{Promise:Fr});var Dr=w(S.Promise,function(e){return e.resolve(42).then(function(){})instanceof e});var zr=!i(function(){return S.Promise.reject(42).then(null,5).then(null,W)});var qr=i(function(){return S.Promise.call(3,W)});var Wr=function(e){var t=e.resolve(5);t.constructor={};var r=e.resolve(t);try{r.then(null,W).then(null,W)}catch(n){return true}return t===r}(S.Promise);var Gr=s&&function(){var e=0;var t=Object.defineProperty({},"then",{get:function(){e+=1}});Promise.resolve(t);return e===1}();var Hr=function BadResolverPromise(e){var t=new Promise(e);e(3,function(){});this.then=t.then;this.constructor=BadResolverPromise};Hr.prototype=Promise.prototype;Hr.all=Promise.all;var Vr=a(function(){return!!Hr.all([1,2])});if(!Dr||!zr||!qr||Wr||!Gr||Vr){Promise=Fr;ne(S,"Promise",Fr)}if(Promise.all.length!==1){var Br=Promise.all;ne(Promise,"all",function all(e){return ce.Call(Br,this,arguments)})}if(Promise.race.length!==1){var Ur=Promise.race;ne(Promise,"race",function race(e){return ce.Call(Ur,this,arguments)})}if(Promise.resolve.length!==1){var $r=Promise.resolve;ne(Promise,"resolve",function resolve(e){return ce.Call($r,this,arguments)})}if(Promise.reject.length!==1){var Jr=Promise.reject;ne(Promise,"reject",function reject(e){return ce.Call(Jr,this,arguments)})}Mt(Promise,"all");Mt(Promise,"race");Mt(Promise,"resolve");Mt(Promise,"reject");Ce(Promise)}var Xr=function(e){var t=n(p(e,function(e,t){e[t]=true;return e},{}));return e.join(":")===t.join(":")};var Kr=Xr(["z","a","bb"]);var Zr=Xr(["z",1,"a","3",2]);if(s){var Yr=function fastkey(e,t){if(!t&&!Kr){return null}if(se(e)){return"^"+ce.ToString(e)}else if(typeof e==="string"){return"$"+e}else if(typeof e==="number"){if(!Zr){return"n"+e}return e}else if(typeof e==="boolean"){return"b"+e}return null};var Qr=function emptyObject(){return Object.create?Object.create(null):{}};var en=function addIterableToMap(e,n,o){if(r(o)||re.string(o)){l(o,function(e){if(!ce.TypeIsObject(e)){throw new TypeError("Iterator value "+e+" is not an entry object")}n.set(e[0],e[1])})}else if(o instanceof e){t(e.prototype.forEach,o,function(e,t){n.set(t,e)})}else{var i,a;if(!se(o)){a=n.set;if(!ce.IsCallable(a)){throw new TypeError("bad map")}i=ce.GetIterator(o)}if(typeof i!=="undefined"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{if(!ce.TypeIsObject(f)){throw new TypeError("Iterator value "+f+" is not an entry object")}t(a,n,f[0],f[1])}catch(s){ce.IteratorClose(i,true);throw s}}}}};var tn=function addIterableToSet(e,n,o){if(r(o)||re.string(o)){l(o,function(e){n.add(e)})}else if(o instanceof e){t(e.prototype.forEach,o,function(e){n.add(e)})}else{var i,a;if(!se(o)){a=n.add;if(!ce.IsCallable(a)){throw new TypeError("bad set")}i=ce.GetIterator(o)}if(typeof i!=="undefined"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{t(a,n,f)}catch(s){ce.IteratorClose(i,true);throw s}}}}};var rn={Map:function(){var e={};var r=function MapEntry(e,t){this.key=e;this.value=t;this.next=null;this.prev=null};r.prototype.isRemoved=function isRemoved(){return this.key===e};var n=function isMap(e){return!!e._es6map};var o=function requireMapSlot(e,t){if(!ce.TypeIsObject(e)||!n(e)){throw new TypeError("Method Map.prototype."+t+" called on incompatible receiver "+ce.ToString(e))}};var i=function MapIterator(e,t){o(e,"[[MapIterator]]");this.head=e._head;this.i=this.head;this.kind=t};i.prototype={isMapIterator:true,next:function next(){if(!this.isMapIterator){throw new TypeError("Not a MapIterator")}var e=this.i;var t=this.kind;var r=this.head;if(typeof this.i==="undefined"){return Ke()}while(e.isRemoved()&&e!==r){e=e.prev}var n;while(e.next!==r){e=e.next;if(!e.isRemoved()){if(t==="key"){n=e.key}else if(t==="value"){n=e.value}else{n=[e.key,e.value]}this.i=e;return Ke(n)}}this.i=void 0;return Ke()}};Me(i.prototype);var a;var u=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}if(this&&this._es6map){throw new TypeError("Bad construction")}var e=Ae(this,Map,a,{_es6map:true,_head:null,_map:G?new G:null,_size:0,_storage:Qr()});var t=new r(null,null);t.next=t.prev=t;e._head=t;if(arguments.length>0){en(Map,e,arguments[0])}return e};a=u.prototype;m.getter(a,"size",function(){if(typeof this._size==="undefined"){throw new TypeError("size method called on incompatible Map")}return this._size});b(a,{get:function get(e){o(this,"get");var t;var r=Yr(e,true);if(r!==null){t=this._storage[r];if(t){return t.value}else{return}}if(this._map){t=V.call(this._map,e);if(t){return t.value}else{return}}var n=this._head;var i=n;while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){return i.value}}},has:function has(e){o(this,"has");var t=Yr(e,true);if(t!==null){return typeof this._storage[t]!=="undefined"}if(this._map){return B.call(this._map,e)}var r=this._head;var n=r;while((n=n.next)!==r){if(ce.SameValueZero(n.key,e)){return true}}return false},set:function set(e,t){o(this,"set");var n=this._head;var i=n;var a;var u=Yr(e,true);if(u!==null){if(typeof this._storage[u]!=="undefined"){this._storage[u].value=t;return this}else{a=this._storage[u]=new r(e,t);i=n.prev}}else if(this._map){if(B.call(this._map,e)){V.call(this._map,e).value=t}else{a=new r(e,t);U.call(this._map,e,a);i=n.prev}}while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){i.value=t;return this}}a=a||new r(e,t);if(ce.SameValue(-0,e)){a.key=+0}a.next=this._head;a.prev=this._head.prev;a.prev.next=a;a.next.prev=a;this._size+=1;return this},"delete":function(t){o(this,"delete");var r=this._head;var n=r;var i=Yr(t,true);if(i!==null){if(typeof this._storage[i]==="undefined"){return false}n=this._storage[i].prev;delete this._storage[i]}else if(this._map){if(!B.call(this._map,t)){return false}n=V.call(this._map,t).prev;H.call(this._map,t)}while((n=n.next)!==r){if(ce.SameValueZero(n.key,t)){n.key=e;n.value=e;n.prev.next=n.next;n.next.prev=n.prev;this._size-=1;return true}}return false},clear:function clear(){o(this,"clear");this._map=G?new G:null;this._size=0;this._storage=Qr();var t=this._head;var r=t;var n=r.next;while((r=n)!==t){r.key=e;r.value=e;n=r.next;r.next=r.prev=t}t.next=t.prev=t},keys:function keys(){o(this,"keys");return new i(this,"key")},values:function values(){o(this,"values");return new i(this,"value")},entries:function entries(){o(this,"entries");return new i(this,"key+value")},forEach:function forEach(e){o(this,"forEach");var r=arguments.length>1?arguments[1]:null;var n=this.entries();for(var i=n.next();!i.done;i=n.next()){if(r){t(e,r,i.value[1],i.value[0],this)}else{e(i.value[1],i.value[0],this)}}}});Me(a,a.entries);return u}(),Set:function(){var e=function isSet(e){return e._es6set&&typeof e._storage!=="undefined"};var r=function requireSetSlot(t,r){if(!ce.TypeIsObject(t)||!e(t)){throw new TypeError("Set.prototype."+r+" called on incompatible receiver "+ce.ToString(t))}};var o;var i=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires "new"')}if(this&&this._es6set){throw new TypeError("Bad construction")}var e=Ae(this,Set,o,{_es6set:true,"[[SetData]]":null,_storage:Qr()});if(!e._es6set){throw new TypeError("bad set")}if(arguments.length>0){tn(Set,e,arguments[0])}return e};o=i.prototype;var a=function(e){var t=e;if(t==="^null"){return null}else if(t==="^undefined"){return void 0}else{var r=t.charAt(0);if(r==="$"){return C(t,1)}else if(r==="n"){return+C(t,1)}else if(r==="b"){return t==="btrue"}}return+t};var u=function ensureMap(e){if(!e["[[SetData]]"]){var t=new rn.Map;e["[[SetData]]"]=t;l(n(e._storage),function(e){var r=a(e);t.set(r,r)});e["[[SetData]]"]=t}e._storage=null};m.getter(i.prototype,"size",function(){r(this,"size");if(this._storage){return n(this._storage).length}u(this);return this["[[SetData]]"].size});b(i.prototype,{has:function has(e){r(this,"has");var t;if(this._storage&&(t=Yr(e))!==null){return!!this._storage[t]}u(this);return this["[[SetData]]"].has(e)},add:function add(e){r(this,"add");var t;if(this._storage&&(t=Yr(e))!==null){this._storage[t]=true;return this}u(this);this["[[SetData]]"].set(e,e);return this},"delete":function(e){r(this,"delete");var t;if(this._storage&&(t=Yr(e))!==null){var n=z(this._storage,t);return delete this._storage[t]&&n}u(this);return this["[[SetData]]"]["delete"](e)},clear:function clear(){r(this,"clear");if(this._storage){this._storage=Qr()}if(this["[[SetData]]"]){this["[[SetData]]"].clear()}},values:function values(){r(this,"values");u(this);return new f(this["[[SetData]]"].values())},entries:function entries(){r(this,"entries");u(this);return new f(this["[[SetData]]"].entries())},forEach:function forEach(e){r(this,"forEach");var n=arguments.length>1?arguments[1]:null;var o=this;u(o);this["[[SetData]]"].forEach(function(r,i){if(n){t(e,n,i,i,o)}else{e(i,i,o)}})}});h(i.prototype,"keys",i.prototype.values,true);Me(i.prototype,i.prototype.values);var f=function SetIterator(e){this.it=e};f.prototype={isSetIterator:true,next:function next(){if(!this.isSetIterator){throw new TypeError("Not a SetIterator")}return this.it.next()}};Me(f.prototype);return i}()};var nn=S.Set&&!Set.prototype["delete"]&&Set.prototype.remove&&Set.prototype.items&&Set.prototype.map&&Array.isArray((new Set).keys);if(nn){S.Set=rn.Set}if(S.Map||S.Set){var on=a(function(){return new Map([[1,2]]).get(1)===2});if(!on){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,S.Map.prototype);return e};S.Map.prototype=O(G.prototype);h(S.Map.prototype,"constructor",S.Map,true);m.preserveToString(S.Map,G)}var an=new Map;var un=function(){var e=new Map([[1,0],[2,0],[3,0],[4,0]]);e.set(-0,e);return e.get(0)===e&&e.get(-0)===e&&e.has(0)&&e.has(-0)}();var fn=an.set(1,2)===an;if(!un||!fn){ne(Map.prototype,"set",function set(e,r){t(U,this,e===0?0:e,r);return this})}if(!un){b(Map.prototype,{get:function get(e){return t(V,this,e===0?0:e)},has:function has(e){return t(B,this,e===0?0:e)}},true);m.preserveToString(Map.prototype.get,V);m.preserveToString(Map.prototype.has,B)}var sn=new Set;var cn=Set.prototype["delete"]&&Set.prototype.add&&Set.prototype.has&&function(e){e["delete"](0);e.add(-0);return!e.has(0)}(sn);var ln=sn.add(1)===sn;if(!cn||!ln){var pn=Set.prototype.add;Set.prototype.add=function add(e){t(pn,this,e===0?0:e);return this};m.preserveToString(Set.prototype.add,pn)}if(!cn){var vn=Set.prototype.has;Set.prototype.has=function has(e){return t(vn,this,e===0?0:e)};m.preserveToString(Set.prototype.has,vn);var yn=Set.prototype["delete"];Set.prototype["delete"]=function SetDelete(e){return t(yn,this,e===0?0:e)};m.preserveToString(Set.prototype["delete"],yn)}var hn=w(S.Map,function(e){var t=new e([]);t.set(42,42);return t instanceof e});var bn=Object.setPrototypeOf&&!hn;var gn=function(){try{return!(S.Map()instanceof S.Map)}catch(e){return e instanceof TypeError}}();if(S.Map.length!==0||bn||!gn){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Map.prototype);return e};S.Map.prototype=G.prototype;h(S.Map.prototype,"constructor",S.Map,true);m.preserveToString(S.Map,G)}var dn=w(S.Set,function(e){var t=new e([]);t.add(42,42);return t instanceof e});var mn=Object.setPrototypeOf&&!dn;var On=function(){try{return!(S.Set()instanceof S.Set)}catch(e){return e instanceof TypeError}}();if(S.Set.length!==0||mn||!On){var wn=S.Set;S.Set=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires "new"')}var e=new wn;if(arguments.length>0){tn(Set,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Set.prototype);return e};S.Set.prototype=wn.prototype;h(S.Set.prototype,"constructor",S.Set,true);m.preserveToString(S.Set,wn)}var jn=new S.Map;var Sn=!a(function(){return jn.keys().next().done});if(typeof S.Map.prototype.clear!=="function"||(new S.Set).size!==0||jn.size!==0||typeof S.Map.prototype.keys!=="function"||typeof S.Set.prototype.keys!=="function"||typeof S.Map.prototype.forEach!=="function"||typeof S.Set.prototype.forEach!=="function"||u(S.Map)||u(S.Set)||typeof jn.keys().next!=="function"||Sn||!hn){b(S,{Map:rn.Map,Set:rn.Set},true)}if(S.Set.prototype.keys!==S.Set.prototype.values){h(S.Set.prototype,"keys",S.Set.prototype.values,true)}Me(Object.getPrototypeOf((new S.Map).keys()));Me(Object.getPrototypeOf((new S.Set).keys()));if(c&&S.Set.prototype.has.name!=="has"){var Tn=S.Set.prototype.has;ne(S.Set.prototype,"has",function has(e){return t(Tn,this,e)})}}b(S,rn);Ce(S.Map);Ce(S.Set)}var In=function throwUnlessTargetIsObject(e){if(!ce.TypeIsObject(e)){throw new TypeError("target must be an object")}};var En={apply:function apply(){return ce.Call(ce.Call,null,arguments)},construct:function construct(e,t){if(!ce.IsConstructor(e)){throw new TypeError("First argument must be a constructor.")}var r=arguments.length>2?arguments[2]:e;if(!ce.IsConstructor(r)){throw new TypeError("new.target must be a constructor.")}return ce.Construct(e,t,r,"internal")},deleteProperty:function deleteProperty(e,t){In(e);if(s){var r=Object.getOwnPropertyDescriptor(e,t);if(r&&!r.configurable){return false}}return delete e[t]},has:function has(e,t){In(e);return t in e}};if(Object.getOwnPropertyNames){Object.assign(En,{ownKeys:function ownKeys(e){In(e);var t=Object.getOwnPropertyNames(e);if(ce.IsCallable(Object.getOwnPropertySymbols)){x(t,Object.getOwnPropertySymbols(e))}return t}})}var Pn=function ConvertExceptionToBoolean(e){return!i(e)};if(Object.preventExtensions){Object.assign(En,{isExtensible:function isExtensible(e){In(e);return Object.isExtensible(e)},preventExtensions:function preventExtensions(e){In(e);return Pn(function(){return Object.preventExtensions(e)})}})}if(s){var Cn=function get(e,t,r){var n=Object.getOwnPropertyDescriptor(e,t);if(!n){var o=Object.getPrototypeOf(e);if(o===null){return void 0}return Cn(o,t,r)}if("value"in n){return n.value}if(n.get){return ce.Call(n.get,r)}return void 0};var Mn=function set(e,r,n,o){var i=Object.getOwnPropertyDescriptor(e,r);if(!i){var a=Object.getPrototypeOf(e);if(a!==null){return Mn(a,r,n,o)}i={value:void 0,writable:true,enumerable:true,configurable:true}}if("value"in i){if(!i.writable){return false}if(!ce.TypeIsObject(o)){return false}var u=Object.getOwnPropertyDescriptor(o,r);if(u){return ae.defineProperty(o,r,{value:n})}else{return ae.defineProperty(o,r,{value:n,writable:true,enumerable:true,configurable:true})}}if(i.set){t(i.set,o,n);return true}return false};Object.assign(En,{defineProperty:function defineProperty(e,t,r){In(e);return Pn(function(){return Object.defineProperty(e,t,r)})},getOwnPropertyDescriptor:function getOwnPropertyDescriptor(e,t){In(e);return Object.getOwnPropertyDescriptor(e,t)},get:function get(e,t){In(e);var r=arguments.length>2?arguments[2]:e;return Cn(e,t,r)},set:function set(e,t,r){In(e);var n=arguments.length>3?arguments[3]:e;return Mn(e,t,r,n)}})}if(Object.getPrototypeOf){var xn=Object.getPrototypeOf;En.getPrototypeOf=function getPrototypeOf(e){In(e);return xn(e)}}if(Object.setPrototypeOf&&En.getPrototypeOf){var Nn=function(e,t){var r=t;while(r){if(e===r){return true}r=En.getPrototypeOf(r)}return false};Object.assign(En,{setPrototypeOf:function setPrototypeOf(e,t){In(e);if(t!==null&&!ce.TypeIsObject(t)){throw new TypeError("proto must be an object or null")}if(t===ae.getPrototypeOf(e)){return true}if(ae.isExtensible&&!ae.isExtensible(e)){return false}if(Nn(e,t)){return false}Object.setPrototypeOf(e,t);return true}})}var An=function(e,t){if(!ce.IsCallable(S.Reflect[e])){h(S.Reflect,e,t)}else{var r=a(function(){S.Reflect[e](1);S.Reflect[e](NaN);S.Reflect[e](true);return true});if(r){ne(S.Reflect,e,t)}}};Object.keys(En).forEach(function(e){An(e,En[e])});var Rn=S.Reflect.getPrototypeOf;if(c&&Rn&&Rn.name!=="getPrototypeOf"){ne(S.Reflect,"getPrototypeOf",function getPrototypeOf(e){return t(Rn,S.Reflect,e)})}if(S.Reflect.setPrototypeOf){if(a(function(){S.Reflect.setPrototypeOf(1,{});return true})){ne(S.Reflect,"setPrototypeOf",En.setPrototypeOf)}}if(S.Reflect.defineProperty){if(!a(function(){var e=!S.Reflect.defineProperty(1,"test",{value:1});var t=typeof Object.preventExtensions!=="function"||!S.Reflect.defineProperty(Object.preventExtensions({}),"test",{});return e&&t})){ne(S.Reflect,"defineProperty",En.defineProperty)}}if(S.Reflect.construct){if(!a(function(){var e=function F(){};return S.Reflect.construct(function(){},[],e)instanceof e})){ne(S.Reflect,"construct",En.construct)}}if(String(new Date(NaN))!=="Invalid Date"){var _n=Date.prototype.toString;var kn=function toString(){var e=+this;if(e!==e){return"Invalid Date"}return ce.Call(_n,this)};ne(Date.prototype,"toString",kn)}var Ln={anchor:function anchor(e){return ce.CreateHTML(this,"a","name",e)},big:function big(){return ce.CreateHTML(this,"big","","")},blink:function blink(){return ce.CreateHTML(this,"blink","","")},bold:function bold(){return ce.CreateHTML(this,"b","","")},fixed:function fixed(){return ce.CreateHTML(this,"tt","","")},fontcolor:function fontcolor(e){return ce.CreateHTML(this,"font","color",e)},fontsize:function fontsize(e){return ce.CreateHTML(this,"font","size",e)},italics:function italics(){return ce.CreateHTML(this,"i","","")},link:function link(e){return ce.CreateHTML(this,"a","href",e)},small:function small(){return ce.CreateHTML(this,"small","","")},strike:function strike(){return ce.CreateHTML(this,"strike","","")},sub:function sub(){return ce.CreateHTML(this,"sub","","")},sup:function sub(){return ce.CreateHTML(this,"sup","","")}};l(Object.keys(Ln),function(e){var r=String.prototype[e];var n=false;if(ce.IsCallable(r)){var o=t(r,"",' " ');var i=P([],o.match(/"/g)).length;n=o!==o.toLowerCase()||i>2}else{n=true}if(n){ne(String.prototype,e,Ln[e])}});var Fn=function(){if(!oe){return false}var e=typeof JSON==="object"&&typeof JSON.stringify==="function"?JSON.stringify:null;if(!e){return false}if(typeof e($())!=="undefined"){return true}if(e([$()])!=="[null]"){return true}var t={a:$()};t[$()]=true;if(e(t)!=="{}"){return true}return false}();var Dn=a(function(){if(!oe){return true}return JSON.stringify(Object($()))==="{}"&&JSON.stringify([Object($())])==="[{}]"});if(Fn||!Dn){var zn=JSON.stringify;ne(JSON,"stringify",function stringify(e){if(typeof e==="symbol"){return}var n;if(arguments.length>1){n=arguments[1]}var o=[e];if(!r(n)){var i=ce.IsCallable(n)?n:null;var a=function(e,r){var n=i?t(i,this,e,r):r;if(typeof n!=="symbol"){if(re.symbol(n)){return Nt({})(n)}else{return n}}};o.push(a)}else{o.push(n)}if(arguments.length>2){o.push(arguments[2])}return zn.apply(this,o)})}return S}); +(function (e, t) { + if (typeof define === 'function' && define.amd) { + define(t); + } else if (typeof exports === 'object') { + module.exports = t(); + } else { + e.returnExports = t(); + } +})(this, function () { + 'use strict'; + var e = Function.call.bind(Function.apply); + var t = Function.call.bind(Function.call); + var r = Array.isArray; + var n = Object.keys; + var o = function notThunker(t) { + return function notThunk() { + return !e(t, this, arguments); + }; + }; + var i = function (e) { + try { + e(); + return false; + } catch (t) { + return true; + } + }; + var a = function valueOrFalseIfThrows(e) { + try { + return e(); + } catch (t) { + return false; + } + }; + var u = o(i); + var f = function () { + return !i(function () { + return Object.defineProperty({}, 'x', { get: function () {} }); + }); + }; + var s = !!Object.defineProperty && f(); + var c = function foo() {}.name === 'foo'; + var l = Function.call.bind(Array.prototype.forEach); + var p = Function.call.bind(Array.prototype.reduce); + var v = Function.call.bind(Array.prototype.filter); + var y = Function.call.bind(Array.prototype.some); + var h = function (e, t, r, n) { + if (!n && t in e) { + return; + } + if (s) { + Object.defineProperty(e, t, { + configurable: true, + enumerable: false, + writable: true, + value: r, + }); + } else { + e[t] = r; + } + }; + var b = function (e, t, r) { + l(n(t), function (n) { + var o = t[n]; + h(e, n, o, !!r); + }); + }; + var g = Function.call.bind(Object.prototype.toString); + var d = + typeof /abc/ === 'function' + ? function IsCallableSlow(e) { + return typeof e === 'function' && g(e) === '[object Function]'; + } + : function IsCallableFast(e) { + return typeof e === 'function'; + }; + var m = { + getter: function (e, t, r) { + if (!s) { + throw new TypeError('getters require true ES5 support'); + } + Object.defineProperty(e, t, { + configurable: true, + enumerable: false, + get: r, + }); + }, + proxy: function (e, t, r) { + if (!s) { + throw new TypeError('getters require true ES5 support'); + } + var n = Object.getOwnPropertyDescriptor(e, t); + Object.defineProperty(r, t, { + configurable: n.configurable, + enumerable: n.enumerable, + get: function getKey() { + return e[t]; + }, + set: function setKey(r) { + e[t] = r; + }, + }); + }, + redefine: function (e, t, r) { + if (s) { + var n = Object.getOwnPropertyDescriptor(e, t); + n.value = r; + Object.defineProperty(e, t, n); + } else { + e[t] = r; + } + }, + defineByDescriptor: function (e, t, r) { + if (s) { + Object.defineProperty(e, t, r); + } else if ('value' in r) { + e[t] = r.value; + } + }, + preserveToString: function (e, t) { + if (t && d(t.toString)) { + h(e, 'toString', t.toString.bind(t), true); + } + }, + }; + var O = + Object.create || + function (e, t) { + var r = function Prototype() {}; + r.prototype = e; + var o = new r(); + if (typeof t !== 'undefined') { + n(t).forEach(function (e) { + m.defineByDescriptor(o, e, t[e]); + }); + } + return o; + }; + var w = function (e, t) { + if (!Object.setPrototypeOf) { + return false; + } + return a(function () { + var r = function Subclass(t) { + var r = new e(t); + Object.setPrototypeOf(r, Subclass.prototype); + return r; + }; + Object.setPrototypeOf(r, e); + r.prototype = O(e.prototype, { constructor: { value: r } }); + return t(r); + }); + }; + var j = function () { + if (typeof self !== 'undefined') { + return self; + } + if (typeof window !== 'undefined') { + return window; + } + if (typeof global !== 'undefined') { + return global; + } + throw new Error('unable to locate global object'); + }; + var S = j(); + var T = S.isFinite; + var I = Function.call.bind(String.prototype.indexOf); + var E = Function.apply.bind(Array.prototype.indexOf); + var P = Function.call.bind(Array.prototype.concat); + var C = Function.call.bind(String.prototype.slice); + var M = Function.call.bind(Array.prototype.push); + var x = Function.apply.bind(Array.prototype.push); + var N = Function.call.bind(Array.prototype.shift); + var A = Math.max; + var R = Math.min; + var _ = Math.floor; + var k = Math.abs; + var L = Math.exp; + var F = Math.log; + var D = Math.sqrt; + var z = Function.call.bind(Object.prototype.hasOwnProperty); + var q; + var W = function () {}; + var G = S.Map; + var H = G && G.prototype['delete']; + var V = G && G.prototype.get; + var B = G && G.prototype.has; + var U = G && G.prototype.set; + var $ = S.Symbol || {}; + var J = $.species || '@@species'; + var X = + Number.isNaN || + function isNaN(e) { + return e !== e; + }; + var K = + Number.isFinite || + function isFinite(e) { + return typeof e === 'number' && T(e); + }; + var Z = d(Math.sign) + ? Math.sign + : function sign(e) { + var t = Number(e); + if (t === 0) { + return t; + } + if (X(t)) { + return t; + } + return t < 0 ? -1 : 1; + }; + var Y = function log1p(e) { + var t = Number(e); + if (t < -1 || X(t)) { + return NaN; + } + if (t === 0 || t === Infinity) { + return t; + } + if (t === -1) { + return -Infinity; + } + return 1 + t - 1 === 0 ? t : t * (F(1 + t) / (1 + t - 1)); + }; + var Q = function isArguments(e) { + return g(e) === '[object Arguments]'; + }; + var ee = function isArguments(e) { + return ( + e !== null && + typeof e === 'object' && + typeof e.length === 'number' && + e.length >= 0 && + g(e) !== '[object Array]' && + g(e.callee) === '[object Function]' + ); + }; + var te = Q(arguments) ? Q : ee; + var re = { + primitive: function (e) { + return e === null || (typeof e !== 'function' && typeof e !== 'object'); + }, + string: function (e) { + return g(e) === '[object String]'; + }, + regex: function (e) { + return g(e) === '[object RegExp]'; + }, + symbol: function (e) { + return typeof S.Symbol === 'function' && typeof e === 'symbol'; + }, + }; + var ne = function overrideNative(e, t, r) { + var n = e[t]; + h(e, t, r, true); + m.preserveToString(e[t], n); + }; + var oe = typeof $ === 'function' && typeof $['for'] === 'function' && re.symbol($()); + var ie = re.symbol($.iterator) ? $.iterator : '_es6-shim iterator_'; + if (S.Set && typeof new S.Set()['@@iterator'] === 'function') { + ie = '@@iterator'; + } + if (!S.Reflect) { + h(S, 'Reflect', {}, true); + } + var ae = S.Reflect; + var ue = String; + var fe = typeof document === 'undefined' || !document ? null : document.all; + var se = + fe == null + ? function isNullOrUndefined(e) { + return e == null; + } + : function isNullOrUndefinedAndNotDocumentAll(e) { + return e == null && e !== fe; + }; + var ce = { + Call: function Call(t, r) { + var n = arguments.length > 2 ? arguments[2] : []; + if (!ce.IsCallable(t)) { + throw new TypeError(t + ' is not a function'); + } + return e(t, r, n); + }, + RequireObjectCoercible: function (e, t) { + if (se(e)) { + throw new TypeError(t || 'Cannot call method on ' + e); + } + return e; + }, + TypeIsObject: function (e) { + if (e === void 0 || e === null || e === true || e === false) { + return false; + } + return typeof e === 'function' || typeof e === 'object' || e === fe; + }, + ToObject: function (e, t) { + return Object(ce.RequireObjectCoercible(e, t)); + }, + IsCallable: d, + IsConstructor: function (e) { + return ce.IsCallable(e); + }, + ToInt32: function (e) { + return ce.ToNumber(e) >> 0; + }, + ToUint32: function (e) { + return ce.ToNumber(e) >>> 0; + }, + ToNumber: function (e) { + if (g(e) === '[object Symbol]') { + throw new TypeError('Cannot convert a Symbol value to a number'); + } + return +e; + }, + ToInteger: function (e) { + var t = ce.ToNumber(e); + if (X(t)) { + return 0; + } + if (t === 0 || !K(t)) { + return t; + } + return (t > 0 ? 1 : -1) * _(k(t)); + }, + ToLength: function (e) { + var t = ce.ToInteger(e); + if (t <= 0) { + return 0; + } + if (t > Number.MAX_SAFE_INTEGER) { + return Number.MAX_SAFE_INTEGER; + } + return t; + }, + SameValue: function (e, t) { + if (e === t) { + if (e === 0) { + return 1 / e === 1 / t; + } + return true; + } + return X(e) && X(t); + }, + SameValueZero: function (e, t) { + return e === t || (X(e) && X(t)); + }, + IsIterable: function (e) { + return ce.TypeIsObject(e) && (typeof e[ie] !== 'undefined' || te(e)); + }, + GetIterator: function (e) { + if (te(e)) { + return new q(e, 'value'); + } + var t = ce.GetMethod(e, ie); + if (!ce.IsCallable(t)) { + throw new TypeError('value is not an iterable'); + } + var r = ce.Call(t, e); + if (!ce.TypeIsObject(r)) { + throw new TypeError('bad iterator'); + } + return r; + }, + GetMethod: function (e, t) { + var r = ce.ToObject(e)[t]; + if (se(r)) { + return void 0; + } + if (!ce.IsCallable(r)) { + throw new TypeError('Method not callable: ' + t); + } + return r; + }, + IteratorComplete: function (e) { + return !!e.done; + }, + IteratorClose: function (e, t) { + var r = ce.GetMethod(e, 'return'); + if (r === void 0) { + return; + } + var n, o; + try { + n = ce.Call(r, e); + } catch (i) { + o = i; + } + if (t) { + return; + } + if (o) { + throw o; + } + if (!ce.TypeIsObject(n)) { + throw new TypeError("Iterator's return method returned a non-object."); + } + }, + IteratorNext: function (e) { + var t = arguments.length > 1 ? e.next(arguments[1]) : e.next(); + if (!ce.TypeIsObject(t)) { + throw new TypeError('bad iterator'); + } + return t; + }, + IteratorStep: function (e) { + var t = ce.IteratorNext(e); + var r = ce.IteratorComplete(t); + return r ? false : t; + }, + Construct: function (e, t, r, n) { + var o = typeof r === 'undefined' ? e : r; + if (!n && ae.construct) { + return ae.construct(e, t, o); + } + var i = o.prototype; + if (!ce.TypeIsObject(i)) { + i = Object.prototype; + } + var a = O(i); + var u = ce.Call(e, a, t); + return ce.TypeIsObject(u) ? u : a; + }, + SpeciesConstructor: function (e, t) { + var r = e.constructor; + if (r === void 0) { + return t; + } + if (!ce.TypeIsObject(r)) { + throw new TypeError('Bad constructor'); + } + var n = r[J]; + if (se(n)) { + return t; + } + if (!ce.IsConstructor(n)) { + throw new TypeError('Bad @@species'); + } + return n; + }, + CreateHTML: function (e, t, r, n) { + var o = ce.ToString(e); + var i = '<' + t; + if (r !== '') { + var a = ce.ToString(n); + var u = a.replace(/"/g, '"'); + i += ' ' + r + '="' + u + '"'; + } + var f = i + '>'; + var s = f + o; + return s + ''; + }, + IsRegExp: function IsRegExp(e) { + if (!ce.TypeIsObject(e)) { + return false; + } + var t = e[$.match]; + if (typeof t !== 'undefined') { + return !!t; + } + return re.regex(e); + }, + ToString: function ToString(e) { + return ue(e); + }, + }; + if (s && oe) { + var le = function defineWellKnownSymbol(e) { + if (re.symbol($[e])) { + return $[e]; + } + var t = $['for']('Symbol.' + e); + Object.defineProperty($, e, { + configurable: false, + enumerable: false, + writable: false, + value: t, + }); + return t; + }; + if (!re.symbol($.search)) { + var pe = le('search'); + var ve = String.prototype.search; + h(RegExp.prototype, pe, function search(e) { + return ce.Call(ve, e, [this]); + }); + var ye = function search(e) { + var t = ce.RequireObjectCoercible(this); + if (!se(e)) { + var r = ce.GetMethod(e, pe); + if (typeof r !== 'undefined') { + return ce.Call(r, e, [t]); + } + } + return ce.Call(ve, t, [ce.ToString(e)]); + }; + ne(String.prototype, 'search', ye); + } + if (!re.symbol($.replace)) { + var he = le('replace'); + var be = String.prototype.replace; + h(RegExp.prototype, he, function replace(e, t) { + return ce.Call(be, e, [this, t]); + }); + var ge = function replace(e, t) { + var r = ce.RequireObjectCoercible(this); + if (!se(e)) { + var n = ce.GetMethod(e, he); + if (typeof n !== 'undefined') { + return ce.Call(n, e, [r, t]); + } + } + return ce.Call(be, r, [ce.ToString(e), t]); + }; + ne(String.prototype, 'replace', ge); + } + if (!re.symbol($.split)) { + var de = le('split'); + var me = String.prototype.split; + h(RegExp.prototype, de, function split(e, t) { + return ce.Call(me, e, [this, t]); + }); + var Oe = function split(e, t) { + var r = ce.RequireObjectCoercible(this); + if (!se(e)) { + var n = ce.GetMethod(e, de); + if (typeof n !== 'undefined') { + return ce.Call(n, e, [r, t]); + } + } + return ce.Call(me, r, [ce.ToString(e), t]); + }; + ne(String.prototype, 'split', Oe); + } + var we = re.symbol($.match); + var je = + we && + (function () { + var e = {}; + e[$.match] = function () { + return 42; + }; + return 'a'.match(e) !== 42; + })(); + if (!we || je) { + var Se = le('match'); + var Te = String.prototype.match; + h(RegExp.prototype, Se, function match(e) { + return ce.Call(Te, e, [this]); + }); + var Ie = function match(e) { + var t = ce.RequireObjectCoercible(this); + if (!se(e)) { + var r = ce.GetMethod(e, Se); + if (typeof r !== 'undefined') { + return ce.Call(r, e, [t]); + } + } + return ce.Call(Te, t, [ce.ToString(e)]); + }; + ne(String.prototype, 'match', Ie); + } + } + var Ee = function wrapConstructor(e, t, r) { + m.preserveToString(t, e); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(e, t); + } + if (s) { + l(Object.getOwnPropertyNames(e), function (n) { + if (n in W || r[n]) { + return; + } + m.proxy(e, n, t); + }); + } else { + l(Object.keys(e), function (n) { + if (n in W || r[n]) { + return; + } + t[n] = e[n]; + }); + } + t.prototype = e.prototype; + m.redefine(e.prototype, 'constructor', t); + }; + var Pe = function () { + return this; + }; + var Ce = function (e) { + if (s && !z(e, J)) { + m.getter(e, J, Pe); + } + }; + var Me = function (e, t) { + var r = + t || + function iterator() { + return this; + }; + h(e, ie, r); + if (!e[ie] && re.symbol(ie)) { + e[ie] = r; + } + }; + var xe = function createDataProperty(e, t, r) { + if (s) { + Object.defineProperty(e, t, { + configurable: true, + enumerable: true, + writable: true, + value: r, + }); + } else { + e[t] = r; + } + }; + var Ne = function createDataPropertyOrThrow(e, t, r) { + xe(e, t, r); + if (!ce.SameValue(e[t], r)) { + throw new TypeError('property is nonconfigurable'); + } + }; + var Ae = function (e, t, r, n) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('Constructor requires `new`: ' + t.name); + } + var o = t.prototype; + if (!ce.TypeIsObject(o)) { + o = r; + } + var i = O(o); + for (var a in n) { + if (z(n, a)) { + var u = n[a]; + h(i, a, u, true); + } + } + return i; + }; + if (String.fromCodePoint && String.fromCodePoint.length !== 1) { + var Re = String.fromCodePoint; + ne(String, 'fromCodePoint', function fromCodePoint(e) { + return ce.Call(Re, this, arguments); + }); + } + var _e = { + fromCodePoint: function fromCodePoint(e) { + var t = []; + var r; + for (var n = 0, o = arguments.length; n < o; n++) { + r = Number(arguments[n]); + if (!ce.SameValue(r, ce.ToInteger(r)) || r < 0 || r > 1114111) { + throw new RangeError('Invalid code point ' + r); + } + if (r < 65536) { + M(t, String.fromCharCode(r)); + } else { + r -= 65536; + M(t, String.fromCharCode((r >> 10) + 55296)); + M(t, String.fromCharCode((r % 1024) + 56320)); + } + } + return t.join(''); + }, + raw: function raw(e) { + var t = ce.ToObject(e, 'bad callSite'); + var r = ce.ToObject(t.raw, 'bad raw value'); + var n = r.length; + var o = ce.ToLength(n); + if (o <= 0) { + return ''; + } + var i = []; + var a = 0; + var u, f, s, c; + while (a < o) { + u = ce.ToString(a); + s = ce.ToString(r[u]); + M(i, s); + if (a + 1 >= o) { + break; + } + f = a + 1 < arguments.length ? arguments[a + 1] : ''; + c = ce.ToString(f); + M(i, c); + a += 1; + } + return i.join(''); + }, + }; + if (String.raw && String.raw({ raw: { 0: 'x', 1: 'y', length: 2 } }) !== 'xy') { + ne(String, 'raw', _e.raw); + } + b(String, _e); + var ke = function repeat(e, t) { + if (t < 1) { + return ''; + } + if (t % 2) { + return repeat(e, t - 1) + e; + } + var r = repeat(e, t / 2); + return r + r; + }; + var Le = Infinity; + var Fe = { + repeat: function repeat(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + var r = ce.ToInteger(e); + if (r < 0 || r >= Le) { + throw new RangeError( + 'repeat count must be less than infinity and not overflow maximum string size' + ); + } + return ke(t, r); + }, + startsWith: function startsWith(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + if (ce.IsRegExp(e)) { + throw new TypeError('Cannot call method "startsWith" with a regex'); + } + var r = ce.ToString(e); + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o = A(ce.ToInteger(n), 0); + return C(t, o, o + r.length) === r; + }, + endsWith: function endsWith(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + if (ce.IsRegExp(e)) { + throw new TypeError('Cannot call method "endsWith" with a regex'); + } + var r = ce.ToString(e); + var n = t.length; + var o; + if (arguments.length > 1) { + o = arguments[1]; + } + var i = typeof o === 'undefined' ? n : ce.ToInteger(o); + var a = R(A(i, 0), n); + return C(t, a - r.length, a) === r; + }, + includes: function includes(e) { + if (ce.IsRegExp(e)) { + throw new TypeError('"includes" does not accept a RegExp'); + } + var t = ce.ToString(e); + var r; + if (arguments.length > 1) { + r = arguments[1]; + } + return I(this, t, r) !== -1; + }, + codePointAt: function codePointAt(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + var r = ce.ToInteger(e); + var n = t.length; + if (r >= 0 && r < n) { + var o = t.charCodeAt(r); + var i = r + 1 === n; + if (o < 55296 || o > 56319 || i) { + return o; + } + var a = t.charCodeAt(r + 1); + if (a < 56320 || a > 57343) { + return o; + } + return (o - 55296) * 1024 + (a - 56320) + 65536; + } + }, + }; + if (String.prototype.includes && 'a'.includes('a', Infinity) !== false) { + ne(String.prototype, 'includes', Fe.includes); + } + if (String.prototype.startsWith && String.prototype.endsWith) { + var De = i(function () { + return '/a/'.startsWith(/a/); + }); + var ze = a(function () { + return 'abc'.startsWith('a', Infinity) === false; + }); + if (!De || !ze) { + ne(String.prototype, 'startsWith', Fe.startsWith); + ne(String.prototype, 'endsWith', Fe.endsWith); + } + } + if (oe) { + var qe = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.startsWith(e); + }); + if (!qe) { + ne(String.prototype, 'startsWith', Fe.startsWith); + } + var We = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.endsWith(e); + }); + if (!We) { + ne(String.prototype, 'endsWith', Fe.endsWith); + } + var Ge = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.includes(e); + }); + if (!Ge) { + ne(String.prototype, 'includes', Fe.includes); + } + } + b(String.prototype, Fe); + var He = [ + '\t\n\x0B\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003', + '\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028', + '\u2029\ufeff', + ].join(''); + var Ve = new RegExp('(^[' + He + ']+)|([' + He + ']+$)', 'g'); + var Be = function trim() { + return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve, ''); + }; + var Ue = ['\x85', '\u200b', '\ufffe'].join(''); + var $e = new RegExp('[' + Ue + ']', 'g'); + var Je = /^[-+]0x[0-9a-f]+$/i; + var Xe = Ue.trim().length !== Ue.length; + h(String.prototype, 'trim', Be, Xe); + var Ke = function (e) { + return { value: e, done: arguments.length === 0 }; + }; + var Ze = function (e) { + ce.RequireObjectCoercible(e); + this._s = ce.ToString(e); + this._i = 0; + }; + Ze.prototype.next = function () { + var e = this._s; + var t = this._i; + if (typeof e === 'undefined' || t >= e.length) { + this._s = void 0; + return Ke(); + } + var r = e.charCodeAt(t); + var n, o; + if (r < 55296 || r > 56319 || t + 1 === e.length) { + o = 1; + } else { + n = e.charCodeAt(t + 1); + o = n < 56320 || n > 57343 ? 1 : 2; + } + this._i = t + o; + return Ke(e.substr(t, o)); + }; + Me(Ze.prototype); + Me(String.prototype, function () { + return new Ze(this); + }); + var Ye = { + from: function from(e) { + var r = this; + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o, i; + if (typeof n === 'undefined') { + o = false; + } else { + if (!ce.IsCallable(n)) { + throw new TypeError('Array.from: when provided, the second argument must be a function'); + } + if (arguments.length > 2) { + i = arguments[2]; + } + o = true; + } + var a = typeof (te(e) || ce.GetMethod(e, ie)) !== 'undefined'; + var u, f, s; + if (a) { + f = ce.IsConstructor(r) ? Object(new r()) : []; + var c = ce.GetIterator(e); + var l, p; + s = 0; + while (true) { + l = ce.IteratorStep(c); + if (l === false) { + break; + } + p = l.value; + try { + if (o) { + p = typeof i === 'undefined' ? n(p, s) : t(n, i, p, s); + } + f[s] = p; + } catch (v) { + ce.IteratorClose(c, true); + throw v; + } + s += 1; + } + u = s; + } else { + var y = ce.ToObject(e); + u = ce.ToLength(y.length); + f = ce.IsConstructor(r) ? Object(new r(u)) : new Array(u); + var h; + for (s = 0; s < u; ++s) { + h = y[s]; + if (o) { + h = typeof i === 'undefined' ? n(h, s) : t(n, i, h, s); + } + Ne(f, s, h); + } + } + f.length = u; + return f; + }, + of: function of() { + var e = arguments.length; + var t = this; + var n = r(t) || !ce.IsCallable(t) ? new Array(e) : ce.Construct(t, [e]); + for (var o = 0; o < e; ++o) { + Ne(n, o, arguments[o]); + } + n.length = e; + return n; + }, + }; + b(Array, Ye); + Ce(Array); + q = function (e, t) { + this.i = 0; + this.array = e; + this.kind = t; + }; + b(q.prototype, { + next: function () { + var e = this.i; + var t = this.array; + if (!(this instanceof q)) { + throw new TypeError('Not an ArrayIterator'); + } + if (typeof t !== 'undefined') { + var r = ce.ToLength(t.length); + for (; e < r; e++) { + var n = this.kind; + var o; + if (n === 'key') { + o = e; + } else if (n === 'value') { + o = t[e]; + } else if (n === 'entry') { + o = [e, t[e]]; + } + this.i = e + 1; + return Ke(o); + } + } + this.array = void 0; + return Ke(); + }, + }); + Me(q.prototype); + var Qe = + Array.of === Ye.of || + (function () { + var e = function Foo(e) { + this.length = e; + }; + e.prototype = []; + var t = Array.of.apply(e, [1, 2]); + return t instanceof e && t.length === 2; + })(); + if (!Qe) { + ne(Array, 'of', Ye.of); + } + var et = { + copyWithin: function copyWithin(e, t) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + var o = ce.ToInteger(e); + var i = ce.ToInteger(t); + var a = o < 0 ? A(n + o, 0) : R(o, n); + var u = i < 0 ? A(n + i, 0) : R(i, n); + var f; + if (arguments.length > 2) { + f = arguments[2]; + } + var s = typeof f === 'undefined' ? n : ce.ToInteger(f); + var c = s < 0 ? A(n + s, 0) : R(s, n); + var l = R(c - u, n - a); + var p = 1; + if (u < a && a < u + l) { + p = -1; + u += l - 1; + a += l - 1; + } + while (l > 0) { + if (u in r) { + r[a] = r[u]; + } else { + delete r[a]; + } + u += p; + a += p; + l -= 1; + } + return r; + }, + fill: function fill(e) { + var t; + if (arguments.length > 1) { + t = arguments[1]; + } + var r; + if (arguments.length > 2) { + r = arguments[2]; + } + var n = ce.ToObject(this); + var o = ce.ToLength(n.length); + t = ce.ToInteger(typeof t === 'undefined' ? 0 : t); + r = ce.ToInteger(typeof r === 'undefined' ? o : r); + var i = t < 0 ? A(o + t, 0) : R(t, o); + var a = r < 0 ? o + r : r; + for (var u = i; u < o && u < a; ++u) { + n[u] = e; + } + return n; + }, + find: function find(e) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + if (!ce.IsCallable(e)) { + throw new TypeError('Array#find: predicate must be a function'); + } + var o = arguments.length > 1 ? arguments[1] : null; + for (var i = 0, a; i < n; i++) { + a = r[i]; + if (o) { + if (t(e, o, a, i, r)) { + return a; + } + } else if (e(a, i, r)) { + return a; + } + } + }, + findIndex: function findIndex(e) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + if (!ce.IsCallable(e)) { + throw new TypeError('Array#findIndex: predicate must be a function'); + } + var o = arguments.length > 1 ? arguments[1] : null; + for (var i = 0; i < n; i++) { + if (o) { + if (t(e, o, r[i], i, r)) { + return i; + } + } else if (e(r[i], i, r)) { + return i; + } + } + return -1; + }, + keys: function keys() { + return new q(this, 'key'); + }, + values: function values() { + return new q(this, 'value'); + }, + entries: function entries() { + return new q(this, 'entry'); + }, + }; + if (Array.prototype.keys && !ce.IsCallable([1].keys().next)) { + delete Array.prototype.keys; + } + if (Array.prototype.entries && !ce.IsCallable([1].entries().next)) { + delete Array.prototype.entries; + } + if ( + Array.prototype.keys && + Array.prototype.entries && + !Array.prototype.values && + Array.prototype[ie] + ) { + b(Array.prototype, { values: Array.prototype[ie] }); + if (re.symbol($.unscopables)) { + Array.prototype[$.unscopables].values = true; + } + } + if (c && Array.prototype.values && Array.prototype.values.name !== 'values') { + var tt = Array.prototype.values; + ne(Array.prototype, 'values', function values() { + return ce.Call(tt, this, arguments); + }); + h(Array.prototype, ie, Array.prototype.values, true); + } + b(Array.prototype, et); + if (1 / [true].indexOf(true, -0) < 0) { + h( + Array.prototype, + 'indexOf', + function indexOf(e) { + var t = E(this, arguments); + if (t === 0 && 1 / t < 0) { + return 0; + } + return t; + }, + true + ); + } + Me(Array.prototype, function () { + return this.values(); + }); + if (Object.getPrototypeOf) { + Me(Object.getPrototypeOf([].values())); + } + var rt = (function () { + return a(function () { + return Array.from({ length: -1 }).length === 0; + }); + })(); + var nt = (function () { + var e = Array.from([0].entries()); + return e.length === 1 && r(e[0]) && e[0][0] === 0 && e[0][1] === 0; + })(); + if (!rt || !nt) { + ne(Array, 'from', Ye.from); + } + var ot = (function () { + return a(function () { + return Array.from([0], void 0); + }); + })(); + if (!ot) { + var it = Array.from; + ne(Array, 'from', function from(e) { + if (arguments.length > 1 && typeof arguments[1] !== 'undefined') { + return ce.Call(it, this, arguments); + } else { + return t(it, this, e); + } + }); + } + var at = -(Math.pow(2, 32) - 1); + var ut = function (e, r) { + var n = { length: at }; + n[r ? (n.length >>> 0) - 1 : 0] = true; + return a(function () { + t( + e, + n, + function () { + throw new RangeError('should not reach here'); + }, + [] + ); + return true; + }); + }; + if (!ut(Array.prototype.forEach)) { + var ft = Array.prototype.forEach; + ne( + Array.prototype, + 'forEach', + function forEach(e) { + return ce.Call(ft, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.map)) { + var st = Array.prototype.map; + ne( + Array.prototype, + 'map', + function map(e) { + return ce.Call(st, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.filter)) { + var ct = Array.prototype.filter; + ne( + Array.prototype, + 'filter', + function filter(e) { + return ce.Call(ct, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.some)) { + var lt = Array.prototype.some; + ne( + Array.prototype, + 'some', + function some(e) { + return ce.Call(lt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.every)) { + var pt = Array.prototype.every; + ne( + Array.prototype, + 'every', + function every(e) { + return ce.Call(pt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.reduce)) { + var vt = Array.prototype.reduce; + ne( + Array.prototype, + 'reduce', + function reduce(e) { + return ce.Call(vt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.reduceRight, true)) { + var yt = Array.prototype.reduceRight; + ne( + Array.prototype, + 'reduceRight', + function reduceRight(e) { + return ce.Call(yt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + var ht = Number('0o10') !== 8; + var bt = Number('0b10') !== 2; + var gt = y(Ue, function (e) { + return Number(e + 0 + e) === 0; + }); + if (ht || bt || gt) { + var dt = Number; + var mt = /^0b[01]+$/i; + var Ot = /^0o[0-7]+$/i; + var wt = mt.test.bind(mt); + var jt = Ot.test.bind(Ot); + var St = function (e) { + var t; + if (typeof e.valueOf === 'function') { + t = e.valueOf(); + if (re.primitive(t)) { + return t; + } + } + if (typeof e.toString === 'function') { + t = e.toString(); + if (re.primitive(t)) { + return t; + } + } + throw new TypeError('No default value'); + }; + var Tt = $e.test.bind($e); + var It = Je.test.bind(Je); + var Et = (function () { + var e = function Number(t) { + var r; + if (arguments.length > 0) { + r = re.primitive(t) ? t : St(t, 'number'); + } else { + r = 0; + } + if (typeof r === 'string') { + r = ce.Call(Be, r); + if (wt(r)) { + r = parseInt(C(r, 2), 2); + } else if (jt(r)) { + r = parseInt(C(r, 2), 8); + } else if (Tt(r) || It(r)) { + r = NaN; + } + } + var n = this; + var o = a(function () { + dt.prototype.valueOf.call(n); + return true; + }); + if (n instanceof e && !o) { + return new dt(r); + } + return dt(r); + }; + return e; + })(); + Ee(dt, Et, {}); + b(Et, { + NaN: dt.NaN, + MAX_VALUE: dt.MAX_VALUE, + MIN_VALUE: dt.MIN_VALUE, + NEGATIVE_INFINITY: dt.NEGATIVE_INFINITY, + POSITIVE_INFINITY: dt.POSITIVE_INFINITY, + }); + Number = Et; + m.redefine(S, 'Number', Et); + } + var Pt = Math.pow(2, 53) - 1; + b(Number, { + MAX_SAFE_INTEGER: Pt, + MIN_SAFE_INTEGER: -Pt, + EPSILON: 2.220446049250313e-16, + parseInt: S.parseInt, + parseFloat: S.parseFloat, + isFinite: K, + isInteger: function isInteger(e) { + return K(e) && ce.ToInteger(e) === e; + }, + isSafeInteger: function isSafeInteger(e) { + return Number.isInteger(e) && k(e) <= Number.MAX_SAFE_INTEGER; + }, + isNaN: X, + }); + h(Number, 'parseInt', S.parseInt, Number.parseInt !== S.parseInt); + if ( + [, 1].find(function () { + return true; + }) === 1 + ) { + ne(Array.prototype, 'find', et.find); + } + if ( + [, 1].findIndex(function () { + return true; + }) !== 0 + ) { + ne(Array.prototype, 'findIndex', et.findIndex); + } + var Ct = Function.bind.call(Function.bind, Object.prototype.propertyIsEnumerable); + var Mt = function ensureEnumerable(e, t) { + if (s && Ct(e, t)) { + Object.defineProperty(e, t, { enumerable: false }); + } + }; + var xt = function sliceArgs() { + var e = Number(this); + var t = arguments.length; + var r = t - e; + var n = new Array(r < 0 ? 0 : r); + for (var o = e; o < t; ++o) { + n[o - e] = arguments[o]; + } + return n; + }; + var Nt = function assignTo(e) { + return function assignToSource(t, r) { + t[r] = e[r]; + return t; + }; + }; + var At = function (e, t) { + var r = n(Object(t)); + var o; + if (ce.IsCallable(Object.getOwnPropertySymbols)) { + o = v(Object.getOwnPropertySymbols(Object(t)), Ct(t)); + } + return p(P(r, o || []), Nt(t), e); + }; + var Rt = { + assign: function (e, t) { + var r = ce.ToObject(e, 'Cannot convert undefined or null to object'); + return p(ce.Call(xt, 1, arguments), At, r); + }, + is: function is(e, t) { + return ce.SameValue(e, t); + }, + }; + var _t = + Object.assign && + Object.preventExtensions && + (function () { + var e = Object.preventExtensions({ 1: 2 }); + try { + Object.assign(e, 'xy'); + } catch (t) { + return e[1] === 'y'; + } + })(); + if (_t) { + ne(Object, 'assign', Rt.assign); + } + b(Object, Rt); + if (s) { + var kt = { + setPrototypeOf: (function (e, r) { + var n; + var o = function (e, t) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('cannot set prototype on a non-object'); + } + if (!(t === null || ce.TypeIsObject(t))) { + throw new TypeError('can only set prototype to an object or null' + t); + } + }; + var i = function (e, r) { + o(e, r); + t(n, e, r); + return e; + }; + try { + n = e.getOwnPropertyDescriptor(e.prototype, r).set; + t(n, {}, null); + } catch (a) { + if (e.prototype !== {}[r]) { + return; + } + n = function (e) { + this[r] = e; + }; + i.polyfill = i(i({}, null), e.prototype) instanceof e; + } + return i; + })(Object, '__proto__'), + }; + b(Object, kt); + } + if ( + Object.setPrototypeOf && + Object.getPrototypeOf && + Object.getPrototypeOf(Object.setPrototypeOf({}, null)) !== null && + Object.getPrototypeOf(Object.create(null)) === null + ) { + (function () { + var e = Object.create(null); + var t = Object.getPrototypeOf; + var r = Object.setPrototypeOf; + Object.getPrototypeOf = function (r) { + var n = t(r); + return n === e ? null : n; + }; + Object.setPrototypeOf = function (t, n) { + var o = n === null ? e : n; + return r(t, o); + }; + Object.setPrototypeOf.polyfill = false; + })(); + } + var Lt = !i(function () { + return Object.keys('foo'); + }); + if (!Lt) { + var Ft = Object.keys; + ne(Object, 'keys', function keys(e) { + return Ft(ce.ToObject(e)); + }); + n = Object.keys; + } + var Dt = i(function () { + return Object.keys(/a/g); + }); + if (Dt) { + var zt = Object.keys; + ne(Object, 'keys', function keys(e) { + if (re.regex(e)) { + var t = []; + for (var r in e) { + if (z(e, r)) { + M(t, r); + } + } + return t; + } + return zt(e); + }); + n = Object.keys; + } + if (Object.getOwnPropertyNames) { + var qt = !i(function () { + return Object.getOwnPropertyNames('foo'); + }); + if (!qt) { + var Wt = typeof window === 'object' ? Object.getOwnPropertyNames(window) : []; + var Gt = Object.getOwnPropertyNames; + ne(Object, 'getOwnPropertyNames', function getOwnPropertyNames(e) { + var t = ce.ToObject(e); + if (g(t) === '[object Window]') { + try { + return Gt(t); + } catch (r) { + return P([], Wt); + } + } + return Gt(t); + }); + } + } + if (Object.getOwnPropertyDescriptor) { + var Ht = !i(function () { + return Object.getOwnPropertyDescriptor('foo', 'bar'); + }); + if (!Ht) { + var Vt = Object.getOwnPropertyDescriptor; + ne(Object, 'getOwnPropertyDescriptor', function getOwnPropertyDescriptor(e, t) { + return Vt(ce.ToObject(e), t); + }); + } + } + if (Object.seal) { + var Bt = !i(function () { + return Object.seal('foo'); + }); + if (!Bt) { + var Ut = Object.seal; + ne(Object, 'seal', function seal(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return Ut(e); + }); + } + } + if (Object.isSealed) { + var $t = !i(function () { + return Object.isSealed('foo'); + }); + if (!$t) { + var Jt = Object.isSealed; + ne(Object, 'isSealed', function isSealed(e) { + if (!ce.TypeIsObject(e)) { + return true; + } + return Jt(e); + }); + } + } + if (Object.freeze) { + var Xt = !i(function () { + return Object.freeze('foo'); + }); + if (!Xt) { + var Kt = Object.freeze; + ne(Object, 'freeze', function freeze(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return Kt(e); + }); + } + } + if (Object.isFrozen) { + var Zt = !i(function () { + return Object.isFrozen('foo'); + }); + if (!Zt) { + var Yt = Object.isFrozen; + ne(Object, 'isFrozen', function isFrozen(e) { + if (!ce.TypeIsObject(e)) { + return true; + } + return Yt(e); + }); + } + } + if (Object.preventExtensions) { + var Qt = !i(function () { + return Object.preventExtensions('foo'); + }); + if (!Qt) { + var er = Object.preventExtensions; + ne(Object, 'preventExtensions', function preventExtensions(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return er(e); + }); + } + } + if (Object.isExtensible) { + var tr = !i(function () { + return Object.isExtensible('foo'); + }); + if (!tr) { + var rr = Object.isExtensible; + ne(Object, 'isExtensible', function isExtensible(e) { + if (!ce.TypeIsObject(e)) { + return false; + } + return rr(e); + }); + } + } + if (Object.getPrototypeOf) { + var nr = !i(function () { + return Object.getPrototypeOf('foo'); + }); + if (!nr) { + var or = Object.getPrototypeOf; + ne(Object, 'getPrototypeOf', function getPrototypeOf(e) { + return or(ce.ToObject(e)); + }); + } + } + var ir = + s && + (function () { + var e = Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags'); + return e && ce.IsCallable(e.get); + })(); + if (s && !ir) { + var ar = function flags() { + if (!ce.TypeIsObject(this)) { + throw new TypeError('Method called on incompatible type: must be an object.'); + } + var e = ''; + if (this.global) { + e += 'g'; + } + if (this.ignoreCase) { + e += 'i'; + } + if (this.multiline) { + e += 'm'; + } + if (this.unicode) { + e += 'u'; + } + if (this.sticky) { + e += 'y'; + } + return e; + }; + m.getter(RegExp.prototype, 'flags', ar); + } + var ur = + s && + a(function () { + return String(new RegExp(/a/g, 'i')) === '/a/i'; + }); + var fr = + oe && + s && + (function () { + var e = /./; + e[$.match] = false; + return RegExp(e) === e; + })(); + var sr = a(function () { + return RegExp.prototype.toString.call({ source: 'abc' }) === '/abc/'; + }); + var cr = + sr && + a(function () { + return RegExp.prototype.toString.call({ source: 'a', flags: 'b' }) === '/a/b'; + }); + if (!sr || !cr) { + var lr = RegExp.prototype.toString; + h( + RegExp.prototype, + 'toString', + function toString() { + var e = ce.RequireObjectCoercible(this); + if (re.regex(e)) { + return t(lr, e); + } + var r = ue(e.source); + var n = ue(e.flags); + return '/' + r + '/' + n; + }, + true + ); + m.preserveToString(RegExp.prototype.toString, lr); + } + if (s && (!ur || fr)) { + var pr = Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags').get; + var vr = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source') || {}; + var yr = function () { + return this.source; + }; + var hr = ce.IsCallable(vr.get) ? vr.get : yr; + var br = RegExp; + var gr = (function () { + return function RegExp(e, t) { + var r = ce.IsRegExp(e); + var n = this instanceof RegExp; + if (!n && r && typeof t === 'undefined' && e.constructor === RegExp) { + return e; + } + var o = e; + var i = t; + if (re.regex(e)) { + o = ce.Call(hr, e); + i = typeof t === 'undefined' ? ce.Call(pr, e) : t; + return new RegExp(o, i); + } else if (r) { + o = e.source; + i = typeof t === 'undefined' ? e.flags : t; + } + return new br(e, t); + }; + })(); + Ee(br, gr, { $input: true }); + RegExp = gr; + m.redefine(S, 'RegExp', gr); + } + if (s) { + var dr = { + input: '$_', + lastMatch: '$&', + lastParen: '$+', + leftContext: '$`', + rightContext: "$'", + }; + l(n(dr), function (e) { + if (e in RegExp && !(dr[e] in RegExp)) { + m.getter(RegExp, dr[e], function get() { + return RegExp[e]; + }); + } + }); + } + Ce(RegExp); + var mr = 1 / Number.EPSILON; + var Or = function roundTiesToEven(e) { + return e + mr - mr; + }; + var wr = Math.pow(2, -23); + var jr = Math.pow(2, 127) * (2 - wr); + var Sr = Math.pow(2, -126); + var Tr = Math.E; + var Ir = Math.LOG2E; + var Er = Math.LOG10E; + var Pr = Number.prototype.clz; + delete Number.prototype.clz; + var Cr = { + acosh: function acosh(e) { + var t = Number(e); + if (X(t) || e < 1) { + return NaN; + } + if (t === 1) { + return 0; + } + if (t === Infinity) { + return t; + } + var r = 1 / (t * t); + if (t < 2) { + return Y(t - 1 + D(1 - r) * t); + } + var n = t / 2; + return Y(n + D(1 - r) * n - 1) + 1 / Ir; + }, + asinh: function asinh(e) { + var t = Number(e); + if (t === 0 || !T(t)) { + return t; + } + var r = k(t); + var n = r * r; + var o = Z(t); + if (r < 1) { + return o * Y(r + n / (D(n + 1) + 1)); + } + return o * (Y(r / 2 + (D(1 + 1 / n) * r) / 2 - 1) + 1 / Ir); + }, + atanh: function atanh(e) { + var t = Number(e); + if (t === 0) { + return t; + } + if (t === -1) { + return -Infinity; + } + if (t === 1) { + return Infinity; + } + if (X(t) || t < -1 || t > 1) { + return NaN; + } + var r = k(t); + return (Z(t) * Y((2 * r) / (1 - r))) / 2; + }, + cbrt: function cbrt(e) { + var t = Number(e); + if (t === 0) { + return t; + } + var r = t < 0; + var n; + if (r) { + t = -t; + } + if (t === Infinity) { + n = Infinity; + } else { + n = L(F(t) / 3); + n = (t / (n * n) + 2 * n) / 3; + } + return r ? -n : n; + }, + clz32: function clz32(e) { + var t = Number(e); + var r = ce.ToUint32(t); + if (r === 0) { + return 32; + } + return Pr ? ce.Call(Pr, r) : 31 - _(F(r + 0.5) * Ir); + }, + cosh: function cosh(e) { + var t = Number(e); + if (t === 0) { + return 1; + } + if (X(t)) { + return NaN; + } + if (!T(t)) { + return Infinity; + } + var r = L(k(t) - 1); + return (r + 1 / (r * Tr * Tr)) * (Tr / 2); + }, + expm1: function expm1(e) { + var t = Number(e); + if (t === -Infinity) { + return -1; + } + if (!T(t) || t === 0) { + return t; + } + if (k(t) > 0.5) { + return L(t) - 1; + } + var r = t; + var n = 0; + var o = 1; + while (n + r !== n) { + n += r; + o += 1; + r *= t / o; + } + return n; + }, + hypot: function hypot(e, t) { + var r = 0; + var n = 0; + for (var o = 0; o < arguments.length; ++o) { + var i = k(Number(arguments[o])); + if (n < i) { + r *= (n / i) * (n / i); + r += 1; + n = i; + } else { + r += i > 0 ? (i / n) * (i / n) : i; + } + } + return n === Infinity ? Infinity : n * D(r); + }, + log2: function log2(e) { + return F(e) * Ir; + }, + log10: function log10(e) { + return F(e) * Er; + }, + log1p: Y, + sign: Z, + sinh: function sinh(e) { + var t = Number(e); + if (!T(t) || t === 0) { + return t; + } + var r = k(t); + if (r < 1) { + var n = Math.expm1(r); + return (Z(t) * n * (1 + 1 / (n + 1))) / 2; + } + var o = L(r - 1); + return Z(t) * (o - 1 / (o * Tr * Tr)) * (Tr / 2); + }, + tanh: function tanh(e) { + var t = Number(e); + if (X(t) || t === 0) { + return t; + } + if (t >= 20) { + return 1; + } + if (t <= -20) { + return -1; + } + return (Math.expm1(t) - Math.expm1(-t)) / (L(t) + L(-t)); + }, + trunc: function trunc(e) { + var t = Number(e); + return t < 0 ? -_(-t) : _(t); + }, + imul: function imul(e, t) { + var r = ce.ToUint32(e); + var n = ce.ToUint32(t); + var o = (r >>> 16) & 65535; + var i = r & 65535; + var a = (n >>> 16) & 65535; + var u = n & 65535; + return (i * u + (((o * u + i * a) << 16) >>> 0)) | 0; + }, + fround: function fround(e) { + var t = Number(e); + if (t === 0 || t === Infinity || t === -Infinity || X(t)) { + return t; + } + var r = Z(t); + var n = k(t); + if (n < Sr) { + return r * Or(n / Sr / wr) * Sr * wr; + } + var o = (1 + wr / Number.EPSILON) * n; + var i = o - (o - n); + if (i > jr || X(i)) { + return r * Infinity; + } + return r * i; + }, + }; + var Mr = function withinULPDistance(e, t, r) { + return k(1 - e / t) / Number.EPSILON < (r || 8); + }; + b(Math, Cr); + h(Math, 'sinh', Cr.sinh, Math.sinh(710) === Infinity); + h(Math, 'cosh', Cr.cosh, Math.cosh(710) === Infinity); + h(Math, 'log1p', Cr.log1p, Math.log1p(-1e-17) !== -1e-17); + h(Math, 'asinh', Cr.asinh, Math.asinh(-1e7) !== -Math.asinh(1e7)); + h(Math, 'asinh', Cr.asinh, Math.asinh(1e300) === Infinity); + h(Math, 'atanh', Cr.atanh, Math.atanh(1e-300) === 0); + h(Math, 'tanh', Cr.tanh, Math.tanh(-2e-17) !== -2e-17); + h(Math, 'acosh', Cr.acosh, Math.acosh(Number.MAX_VALUE) === Infinity); + h(Math, 'acosh', Cr.acosh, !Mr(Math.acosh(1 + Number.EPSILON), Math.sqrt(2 * Number.EPSILON))); + h(Math, 'cbrt', Cr.cbrt, !Mr(Math.cbrt(1e-300), 1e-100)); + h(Math, 'sinh', Cr.sinh, Math.sinh(-2e-17) !== -2e-17); + var xr = Math.expm1(10); + h(Math, 'expm1', Cr.expm1, xr > 22025.465794806718 || xr < 22025.465794806718); + var Nr = Math.round; + var Ar = + Math.round(0.5 - Number.EPSILON / 4) === 0 && Math.round(-0.5 + Number.EPSILON / 3.99) === 1; + var Rr = mr + 1; + var _r = 2 * mr - 1; + var kr = [Rr, _r].every(function (e) { + return Math.round(e) === e; + }); + h( + Math, + 'round', + function round(e) { + var t = _(e); + var r = t === -1 ? -0 : t + 1; + return e - t < 0.5 ? t : r; + }, + !Ar || !kr + ); + m.preserveToString(Math.round, Nr); + var Lr = Math.imul; + if (Math.imul(4294967295, 5) !== -5) { + Math.imul = Cr.imul; + m.preserveToString(Math.imul, Lr); + } + if (Math.imul.length !== 2) { + ne(Math, 'imul', function imul(e, t) { + return ce.Call(Lr, Math, arguments); + }); + } + var Fr = (function () { + var e = S.setTimeout; + if (typeof e !== 'function' && typeof e !== 'object') { + return; + } + ce.IsPromise = function (e) { + if (!ce.TypeIsObject(e)) { + return false; + } + if (typeof e._promise === 'undefined') { + return false; + } + return true; + }; + var r = function (e) { + if (!ce.IsConstructor(e)) { + throw new TypeError('Bad promise constructor'); + } + var t = this; + var r = function (e, r) { + if (t.resolve !== void 0 || t.reject !== void 0) { + throw new TypeError('Bad Promise implementation!'); + } + t.resolve = e; + t.reject = r; + }; + t.resolve = void 0; + t.reject = void 0; + t.promise = new e(r); + if (!(ce.IsCallable(t.resolve) && ce.IsCallable(t.reject))) { + throw new TypeError('Bad promise constructor'); + } + }; + var n; + if (typeof window !== 'undefined' && ce.IsCallable(window.postMessage)) { + n = function () { + var e = []; + var t = 'zero-timeout-message'; + var r = function (r) { + M(e, r); + window.postMessage(t, '*'); + }; + var n = function (r) { + if (r.source === window && r.data === t) { + r.stopPropagation(); + if (e.length === 0) { + return; + } + var n = N(e); + n(); + } + }; + window.addEventListener('message', n, true); + return r; + }; + } + var o = function () { + var e = S.Promise; + var t = e && e.resolve && e.resolve(); + return ( + t && + function (e) { + return t.then(e); + } + ); + }; + var i = ce.IsCallable(S.setImmediate) + ? S.setImmediate + : typeof process === 'object' && process.nextTick + ? process.nextTick + : o() || + (ce.IsCallable(n) + ? n() + : function (t) { + e(t, 0); + }); + var a = function (e) { + return e; + }; + var u = function (e) { + throw e; + }; + var f = 0; + var s = 1; + var c = 2; + var l = 0; + var p = 1; + var v = 2; + var y = {}; + var h = function (e, t, r) { + i(function () { + g(e, t, r); + }); + }; + var g = function (e, t, r) { + var n, o; + if (t === y) { + return e(r); + } + try { + n = e(r); + o = t.resolve; + } catch (i) { + n = i; + o = t.reject; + } + o(n); + }; + var d = function (e, t) { + var r = e._promise; + var n = r.reactionLength; + if (n > 0) { + h(r.fulfillReactionHandler0, r.reactionCapability0, t); + r.fulfillReactionHandler0 = void 0; + r.rejectReactions0 = void 0; + r.reactionCapability0 = void 0; + if (n > 1) { + for (var o = 1, i = 0; o < n; o++, i += 3) { + h(r[i + l], r[i + v], t); + e[i + l] = void 0; + e[i + p] = void 0; + e[i + v] = void 0; + } + } + } + r.result = t; + r.state = s; + r.reactionLength = 0; + }; + var m = function (e, t) { + var r = e._promise; + var n = r.reactionLength; + if (n > 0) { + h(r.rejectReactionHandler0, r.reactionCapability0, t); + r.fulfillReactionHandler0 = void 0; + r.rejectReactions0 = void 0; + r.reactionCapability0 = void 0; + if (n > 1) { + for (var o = 1, i = 0; o < n; o++, i += 3) { + h(r[i + p], r[i + v], t); + e[i + l] = void 0; + e[i + p] = void 0; + e[i + v] = void 0; + } + } + } + r.result = t; + r.state = c; + r.reactionLength = 0; + }; + var O = function (e) { + var t = false; + var r = function (r) { + var n; + if (t) { + return; + } + t = true; + if (r === e) { + return m(e, new TypeError('Self resolution')); + } + if (!ce.TypeIsObject(r)) { + return d(e, r); + } + try { + n = r.then; + } catch (o) { + return m(e, o); + } + if (!ce.IsCallable(n)) { + return d(e, r); + } + i(function () { + j(e, r, n); + }); + }; + var n = function (r) { + if (t) { + return; + } + t = true; + return m(e, r); + }; + return { resolve: r, reject: n }; + }; + var w = function (e, r, n, o) { + if (e === I) { + t(e, r, n, o, y); + } else { + t(e, r, n, o); + } + }; + var j = function (e, t, r) { + var n = O(e); + var o = n.resolve; + var i = n.reject; + try { + w(r, t, o, i); + } catch (a) { + i(a); + } + }; + var T, I; + var E = (function () { + var e = function Promise(t) { + if (!(this instanceof e)) { + throw new TypeError('Constructor Promise requires "new"'); + } + if (this && this._promise) { + throw new TypeError('Bad construction'); + } + if (!ce.IsCallable(t)) { + throw new TypeError('not a valid resolver'); + } + var r = Ae(this, e, T, { + _promise: { + result: void 0, + state: f, + reactionLength: 0, + fulfillReactionHandler0: void 0, + rejectReactionHandler0: void 0, + reactionCapability0: void 0, + }, + }); + var n = O(r); + var o = n.reject; + try { + t(n.resolve, o); + } catch (i) { + o(i); + } + return r; + }; + return e; + })(); + T = E.prototype; + var P = function (e, t, r, n) { + var o = false; + return function (i) { + if (o) { + return; + } + o = true; + t[e] = i; + if (--n.count === 0) { + var a = r.resolve; + a(t); + } + }; + }; + var C = function (e, t, r) { + var n = e.iterator; + var o = []; + var i = { count: 1 }; + var a, u; + var f = 0; + while (true) { + try { + a = ce.IteratorStep(n); + if (a === false) { + e.done = true; + break; + } + u = a.value; + } catch (s) { + e.done = true; + throw s; + } + o[f] = void 0; + var c = t.resolve(u); + var l = P(f, o, r, i); + i.count += 1; + w(c.then, c, l, r.reject); + f += 1; + } + if (--i.count === 0) { + var p = r.resolve; + p(o); + } + return r.promise; + }; + var x = function (e, t, r) { + var n = e.iterator; + var o, i, a; + while (true) { + try { + o = ce.IteratorStep(n); + if (o === false) { + e.done = true; + break; + } + i = o.value; + } catch (u) { + e.done = true; + throw u; + } + a = t.resolve(i); + w(a.then, a, r.resolve, r.reject); + } + return r.promise; + }; + b(E, { + all: function all(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Promise is not object'); + } + var n = new r(t); + var o, i; + try { + o = ce.GetIterator(e); + i = { iterator: o, done: false }; + return C(i, t, n); + } catch (a) { + var u = a; + if (i && !i.done) { + try { + ce.IteratorClose(o, true); + } catch (f) { + u = f; + } + } + var s = n.reject; + s(u); + return n.promise; + } + }, + race: function race(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Promise is not object'); + } + var n = new r(t); + var o, i; + try { + o = ce.GetIterator(e); + i = { iterator: o, done: false }; + return x(i, t, n); + } catch (a) { + var u = a; + if (i && !i.done) { + try { + ce.IteratorClose(o, true); + } catch (f) { + u = f; + } + } + var s = n.reject; + s(u); + return n.promise; + } + }, + reject: function reject(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Bad promise constructor'); + } + var n = new r(t); + var o = n.reject; + o(e); + return n.promise; + }, + resolve: function resolve(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Bad promise constructor'); + } + if (ce.IsPromise(e)) { + var n = e.constructor; + if (n === t) { + return e; + } + } + var o = new r(t); + var i = o.resolve; + i(e); + return o.promise; + }, + }); + b(T, { + catch: function (e) { + return this.then(null, e); + }, + then: function then(e, t) { + var n = this; + if (!ce.IsPromise(n)) { + throw new TypeError('not a promise'); + } + var o = ce.SpeciesConstructor(n, E); + var i; + var b = arguments.length > 2 && arguments[2] === y; + if (b && o === E) { + i = y; + } else { + i = new r(o); + } + var g = ce.IsCallable(e) ? e : a; + var d = ce.IsCallable(t) ? t : u; + var m = n._promise; + var O; + if (m.state === f) { + if (m.reactionLength === 0) { + m.fulfillReactionHandler0 = g; + m.rejectReactionHandler0 = d; + m.reactionCapability0 = i; + } else { + var w = 3 * (m.reactionLength - 1); + m[w + l] = g; + m[w + p] = d; + m[w + v] = i; + } + m.reactionLength += 1; + } else if (m.state === s) { + O = m.result; + h(g, i, O); + } else if (m.state === c) { + O = m.result; + h(d, i, O); + } else { + throw new TypeError('unexpected Promise state'); + } + return i.promise; + }, + }); + y = new r(E); + I = T.then; + return E; + })(); + if (S.Promise) { + delete S.Promise.accept; + delete S.Promise.defer; + delete S.Promise.prototype.chain; + } + if (typeof Fr === 'function') { + b(S, { Promise: Fr }); + var Dr = w(S.Promise, function (e) { + return e.resolve(42).then(function () {}) instanceof e; + }); + var zr = !i(function () { + return S.Promise.reject(42).then(null, 5).then(null, W); + }); + var qr = i(function () { + return S.Promise.call(3, W); + }); + var Wr = (function (e) { + var t = e.resolve(5); + t.constructor = {}; + var r = e.resolve(t); + try { + r.then(null, W).then(null, W); + } catch (n) { + return true; + } + return t === r; + })(S.Promise); + var Gr = + s && + (function () { + var e = 0; + var t = Object.defineProperty({}, 'then', { + get: function () { + e += 1; + }, + }); + Promise.resolve(t); + return e === 1; + })(); + var Hr = function BadResolverPromise(e) { + var t = new Promise(e); + e(3, function () {}); + this.then = t.then; + this.constructor = BadResolverPromise; + }; + Hr.prototype = Promise.prototype; + Hr.all = Promise.all; + var Vr = a(function () { + return !!Hr.all([1, 2]); + }); + if (!Dr || !zr || !qr || Wr || !Gr || Vr) { + Promise = Fr; + ne(S, 'Promise', Fr); + } + if (Promise.all.length !== 1) { + var Br = Promise.all; + ne(Promise, 'all', function all(e) { + return ce.Call(Br, this, arguments); + }); + } + if (Promise.race.length !== 1) { + var Ur = Promise.race; + ne(Promise, 'race', function race(e) { + return ce.Call(Ur, this, arguments); + }); + } + if (Promise.resolve.length !== 1) { + var $r = Promise.resolve; + ne(Promise, 'resolve', function resolve(e) { + return ce.Call($r, this, arguments); + }); + } + if (Promise.reject.length !== 1) { + var Jr = Promise.reject; + ne(Promise, 'reject', function reject(e) { + return ce.Call(Jr, this, arguments); + }); + } + Mt(Promise, 'all'); + Mt(Promise, 'race'); + Mt(Promise, 'resolve'); + Mt(Promise, 'reject'); + Ce(Promise); + } + var Xr = function (e) { + var t = n( + p( + e, + function (e, t) { + e[t] = true; + return e; + }, + {} + ) + ); + return e.join(':') === t.join(':'); + }; + var Kr = Xr(['z', 'a', 'bb']); + var Zr = Xr(['z', 1, 'a', '3', 2]); + if (s) { + var Yr = function fastkey(e, t) { + if (!t && !Kr) { + return null; + } + if (se(e)) { + return '^' + ce.ToString(e); + } else if (typeof e === 'string') { + return '$' + e; + } else if (typeof e === 'number') { + if (!Zr) { + return 'n' + e; + } + return e; + } else if (typeof e === 'boolean') { + return 'b' + e; + } + return null; + }; + var Qr = function emptyObject() { + return Object.create ? Object.create(null) : {}; + }; + var en = function addIterableToMap(e, n, o) { + if (r(o) || re.string(o)) { + l(o, function (e) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('Iterator value ' + e + ' is not an entry object'); + } + n.set(e[0], e[1]); + }); + } else if (o instanceof e) { + t(e.prototype.forEach, o, function (e, t) { + n.set(t, e); + }); + } else { + var i, a; + if (!se(o)) { + a = n.set; + if (!ce.IsCallable(a)) { + throw new TypeError('bad map'); + } + i = ce.GetIterator(o); + } + if (typeof i !== 'undefined') { + while (true) { + var u = ce.IteratorStep(i); + if (u === false) { + break; + } + var f = u.value; + try { + if (!ce.TypeIsObject(f)) { + throw new TypeError('Iterator value ' + f + ' is not an entry object'); + } + t(a, n, f[0], f[1]); + } catch (s) { + ce.IteratorClose(i, true); + throw s; + } + } + } + } + }; + var tn = function addIterableToSet(e, n, o) { + if (r(o) || re.string(o)) { + l(o, function (e) { + n.add(e); + }); + } else if (o instanceof e) { + t(e.prototype.forEach, o, function (e) { + n.add(e); + }); + } else { + var i, a; + if (!se(o)) { + a = n.add; + if (!ce.IsCallable(a)) { + throw new TypeError('bad set'); + } + i = ce.GetIterator(o); + } + if (typeof i !== 'undefined') { + while (true) { + var u = ce.IteratorStep(i); + if (u === false) { + break; + } + var f = u.value; + try { + t(a, n, f); + } catch (s) { + ce.IteratorClose(i, true); + throw s; + } + } + } + } + }; + var rn = { + Map: (function () { + var e = {}; + var r = function MapEntry(e, t) { + this.key = e; + this.value = t; + this.next = null; + this.prev = null; + }; + r.prototype.isRemoved = function isRemoved() { + return this.key === e; + }; + var n = function isMap(e) { + return !!e._es6map; + }; + var o = function requireMapSlot(e, t) { + if (!ce.TypeIsObject(e) || !n(e)) { + throw new TypeError( + 'Method Map.prototype.' + t + ' called on incompatible receiver ' + ce.ToString(e) + ); + } + }; + var i = function MapIterator(e, t) { + o(e, '[[MapIterator]]'); + this.head = e._head; + this.i = this.head; + this.kind = t; + }; + i.prototype = { + isMapIterator: true, + next: function next() { + if (!this.isMapIterator) { + throw new TypeError('Not a MapIterator'); + } + var e = this.i; + var t = this.kind; + var r = this.head; + if (typeof this.i === 'undefined') { + return Ke(); + } + while (e.isRemoved() && e !== r) { + e = e.prev; + } + var n; + while (e.next !== r) { + e = e.next; + if (!e.isRemoved()) { + if (t === 'key') { + n = e.key; + } else if (t === 'value') { + n = e.value; + } else { + n = [e.key, e.value]; + } + this.i = e; + return Ke(n); + } + } + this.i = void 0; + return Ke(); + }, + }; + Me(i.prototype); + var a; + var u = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + if (this && this._es6map) { + throw new TypeError('Bad construction'); + } + var e = Ae(this, Map, a, { + _es6map: true, + _head: null, + _map: G ? new G() : null, + _size: 0, + _storage: Qr(), + }); + var t = new r(null, null); + t.next = t.prev = t; + e._head = t; + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + return e; + }; + a = u.prototype; + m.getter(a, 'size', function () { + if (typeof this._size === 'undefined') { + throw new TypeError('size method called on incompatible Map'); + } + return this._size; + }); + b(a, { + get: function get(e) { + o(this, 'get'); + var t; + var r = Yr(e, true); + if (r !== null) { + t = this._storage[r]; + if (t) { + return t.value; + } else { + return; + } + } + if (this._map) { + t = V.call(this._map, e); + if (t) { + return t.value; + } else { + return; + } + } + var n = this._head; + var i = n; + while ((i = i.next) !== n) { + if (ce.SameValueZero(i.key, e)) { + return i.value; + } + } + }, + has: function has(e) { + o(this, 'has'); + var t = Yr(e, true); + if (t !== null) { + return typeof this._storage[t] !== 'undefined'; + } + if (this._map) { + return B.call(this._map, e); + } + var r = this._head; + var n = r; + while ((n = n.next) !== r) { + if (ce.SameValueZero(n.key, e)) { + return true; + } + } + return false; + }, + set: function set(e, t) { + o(this, 'set'); + var n = this._head; + var i = n; + var a; + var u = Yr(e, true); + if (u !== null) { + if (typeof this._storage[u] !== 'undefined') { + this._storage[u].value = t; + return this; + } else { + a = this._storage[u] = new r(e, t); + i = n.prev; + } + } else if (this._map) { + if (B.call(this._map, e)) { + V.call(this._map, e).value = t; + } else { + a = new r(e, t); + U.call(this._map, e, a); + i = n.prev; + } + } + while ((i = i.next) !== n) { + if (ce.SameValueZero(i.key, e)) { + i.value = t; + return this; + } + } + a = a || new r(e, t); + if (ce.SameValue(-0, e)) { + a.key = +0; + } + a.next = this._head; + a.prev = this._head.prev; + a.prev.next = a; + a.next.prev = a; + this._size += 1; + return this; + }, + delete: function (t) { + o(this, 'delete'); + var r = this._head; + var n = r; + var i = Yr(t, true); + if (i !== null) { + if (typeof this._storage[i] === 'undefined') { + return false; + } + n = this._storage[i].prev; + delete this._storage[i]; + } else if (this._map) { + if (!B.call(this._map, t)) { + return false; + } + n = V.call(this._map, t).prev; + H.call(this._map, t); + } + while ((n = n.next) !== r) { + if (ce.SameValueZero(n.key, t)) { + n.key = e; + n.value = e; + n.prev.next = n.next; + n.next.prev = n.prev; + this._size -= 1; + return true; + } + } + return false; + }, + clear: function clear() { + o(this, 'clear'); + this._map = G ? new G() : null; + this._size = 0; + this._storage = Qr(); + var t = this._head; + var r = t; + var n = r.next; + while ((r = n) !== t) { + r.key = e; + r.value = e; + n = r.next; + r.next = r.prev = t; + } + t.next = t.prev = t; + }, + keys: function keys() { + o(this, 'keys'); + return new i(this, 'key'); + }, + values: function values() { + o(this, 'values'); + return new i(this, 'value'); + }, + entries: function entries() { + o(this, 'entries'); + return new i(this, 'key+value'); + }, + forEach: function forEach(e) { + o(this, 'forEach'); + var r = arguments.length > 1 ? arguments[1] : null; + var n = this.entries(); + for (var i = n.next(); !i.done; i = n.next()) { + if (r) { + t(e, r, i.value[1], i.value[0], this); + } else { + e(i.value[1], i.value[0], this); + } + } + }, + }); + Me(a, a.entries); + return u; + })(), + Set: (function () { + var e = function isSet(e) { + return e._es6set && typeof e._storage !== 'undefined'; + }; + var r = function requireSetSlot(t, r) { + if (!ce.TypeIsObject(t) || !e(t)) { + throw new TypeError( + 'Set.prototype.' + r + ' called on incompatible receiver ' + ce.ToString(t) + ); + } + }; + var o; + var i = function Set() { + if (!(this instanceof Set)) { + throw new TypeError('Constructor Set requires "new"'); + } + if (this && this._es6set) { + throw new TypeError('Bad construction'); + } + var e = Ae(this, Set, o, { + _es6set: true, + '[[SetData]]': null, + _storage: Qr(), + }); + if (!e._es6set) { + throw new TypeError('bad set'); + } + if (arguments.length > 0) { + tn(Set, e, arguments[0]); + } + return e; + }; + o = i.prototype; + var a = function (e) { + var t = e; + if (t === '^null') { + return null; + } else if (t === '^undefined') { + return void 0; + } else { + var r = t.charAt(0); + if (r === '$') { + return C(t, 1); + } else if (r === 'n') { + return +C(t, 1); + } else if (r === 'b') { + return t === 'btrue'; + } + } + return +t; + }; + var u = function ensureMap(e) { + if (!e['[[SetData]]']) { + var t = new rn.Map(); + e['[[SetData]]'] = t; + l(n(e._storage), function (e) { + var r = a(e); + t.set(r, r); + }); + e['[[SetData]]'] = t; + } + e._storage = null; + }; + m.getter(i.prototype, 'size', function () { + r(this, 'size'); + if (this._storage) { + return n(this._storage).length; + } + u(this); + return this['[[SetData]]'].size; + }); + b(i.prototype, { + has: function has(e) { + r(this, 'has'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + return !!this._storage[t]; + } + u(this); + return this['[[SetData]]'].has(e); + }, + add: function add(e) { + r(this, 'add'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + this._storage[t] = true; + return this; + } + u(this); + this['[[SetData]]'].set(e, e); + return this; + }, + delete: function (e) { + r(this, 'delete'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + var n = z(this._storage, t); + return delete this._storage[t] && n; + } + u(this); + return this['[[SetData]]']['delete'](e); + }, + clear: function clear() { + r(this, 'clear'); + if (this._storage) { + this._storage = Qr(); + } + if (this['[[SetData]]']) { + this['[[SetData]]'].clear(); + } + }, + values: function values() { + r(this, 'values'); + u(this); + return new f(this['[[SetData]]'].values()); + }, + entries: function entries() { + r(this, 'entries'); + u(this); + return new f(this['[[SetData]]'].entries()); + }, + forEach: function forEach(e) { + r(this, 'forEach'); + var n = arguments.length > 1 ? arguments[1] : null; + var o = this; + u(o); + this['[[SetData]]'].forEach(function (r, i) { + if (n) { + t(e, n, i, i, o); + } else { + e(i, i, o); + } + }); + }, + }); + h(i.prototype, 'keys', i.prototype.values, true); + Me(i.prototype, i.prototype.values); + var f = function SetIterator(e) { + this.it = e; + }; + f.prototype = { + isSetIterator: true, + next: function next() { + if (!this.isSetIterator) { + throw new TypeError('Not a SetIterator'); + } + return this.it.next(); + }, + }; + Me(f.prototype); + return i; + })(), + }; + var nn = + S.Set && + !Set.prototype['delete'] && + Set.prototype.remove && + Set.prototype.items && + Set.prototype.map && + Array.isArray(new Set().keys); + if (nn) { + S.Set = rn.Set; + } + if (S.Map || S.Set) { + var on = a(function () { + return new Map([[1, 2]]).get(1) === 2; + }); + if (!on) { + S.Map = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + var e = new G(); + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, S.Map.prototype); + return e; + }; + S.Map.prototype = O(G.prototype); + h(S.Map.prototype, 'constructor', S.Map, true); + m.preserveToString(S.Map, G); + } + var an = new Map(); + var un = (function () { + var e = new Map([ + [1, 0], + [2, 0], + [3, 0], + [4, 0], + ]); + e.set(-0, e); + return e.get(0) === e && e.get(-0) === e && e.has(0) && e.has(-0); + })(); + var fn = an.set(1, 2) === an; + if (!un || !fn) { + ne(Map.prototype, 'set', function set(e, r) { + t(U, this, e === 0 ? 0 : e, r); + return this; + }); + } + if (!un) { + b( + Map.prototype, + { + get: function get(e) { + return t(V, this, e === 0 ? 0 : e); + }, + has: function has(e) { + return t(B, this, e === 0 ? 0 : e); + }, + }, + true + ); + m.preserveToString(Map.prototype.get, V); + m.preserveToString(Map.prototype.has, B); + } + var sn = new Set(); + var cn = + Set.prototype['delete'] && + Set.prototype.add && + Set.prototype.has && + (function (e) { + e['delete'](0); + e.add(-0); + return !e.has(0); + })(sn); + var ln = sn.add(1) === sn; + if (!cn || !ln) { + var pn = Set.prototype.add; + Set.prototype.add = function add(e) { + t(pn, this, e === 0 ? 0 : e); + return this; + }; + m.preserveToString(Set.prototype.add, pn); + } + if (!cn) { + var vn = Set.prototype.has; + Set.prototype.has = function has(e) { + return t(vn, this, e === 0 ? 0 : e); + }; + m.preserveToString(Set.prototype.has, vn); + var yn = Set.prototype['delete']; + Set.prototype['delete'] = function SetDelete(e) { + return t(yn, this, e === 0 ? 0 : e); + }; + m.preserveToString(Set.prototype['delete'], yn); + } + var hn = w(S.Map, function (e) { + var t = new e([]); + t.set(42, 42); + return t instanceof e; + }); + var bn = Object.setPrototypeOf && !hn; + var gn = (function () { + try { + return !(S.Map() instanceof S.Map); + } catch (e) { + return e instanceof TypeError; + } + })(); + if (S.Map.length !== 0 || bn || !gn) { + S.Map = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + var e = new G(); + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, Map.prototype); + return e; + }; + S.Map.prototype = G.prototype; + h(S.Map.prototype, 'constructor', S.Map, true); + m.preserveToString(S.Map, G); + } + var dn = w(S.Set, function (e) { + var t = new e([]); + t.add(42, 42); + return t instanceof e; + }); + var mn = Object.setPrototypeOf && !dn; + var On = (function () { + try { + return !(S.Set() instanceof S.Set); + } catch (e) { + return e instanceof TypeError; + } + })(); + if (S.Set.length !== 0 || mn || !On) { + var wn = S.Set; + S.Set = function Set() { + if (!(this instanceof Set)) { + throw new TypeError('Constructor Set requires "new"'); + } + var e = new wn(); + if (arguments.length > 0) { + tn(Set, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, Set.prototype); + return e; + }; + S.Set.prototype = wn.prototype; + h(S.Set.prototype, 'constructor', S.Set, true); + m.preserveToString(S.Set, wn); + } + var jn = new S.Map(); + var Sn = !a(function () { + return jn.keys().next().done; + }); + if ( + typeof S.Map.prototype.clear !== 'function' || + new S.Set().size !== 0 || + jn.size !== 0 || + typeof S.Map.prototype.keys !== 'function' || + typeof S.Set.prototype.keys !== 'function' || + typeof S.Map.prototype.forEach !== 'function' || + typeof S.Set.prototype.forEach !== 'function' || + u(S.Map) || + u(S.Set) || + typeof jn.keys().next !== 'function' || + Sn || + !hn + ) { + b(S, { Map: rn.Map, Set: rn.Set }, true); + } + if (S.Set.prototype.keys !== S.Set.prototype.values) { + h(S.Set.prototype, 'keys', S.Set.prototype.values, true); + } + Me(Object.getPrototypeOf(new S.Map().keys())); + Me(Object.getPrototypeOf(new S.Set().keys())); + if (c && S.Set.prototype.has.name !== 'has') { + var Tn = S.Set.prototype.has; + ne(S.Set.prototype, 'has', function has(e) { + return t(Tn, this, e); + }); + } + } + b(S, rn); + Ce(S.Map); + Ce(S.Set); + } + var In = function throwUnlessTargetIsObject(e) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('target must be an object'); + } + }; + var En = { + apply: function apply() { + return ce.Call(ce.Call, null, arguments); + }, + construct: function construct(e, t) { + if (!ce.IsConstructor(e)) { + throw new TypeError('First argument must be a constructor.'); + } + var r = arguments.length > 2 ? arguments[2] : e; + if (!ce.IsConstructor(r)) { + throw new TypeError('new.target must be a constructor.'); + } + return ce.Construct(e, t, r, 'internal'); + }, + deleteProperty: function deleteProperty(e, t) { + In(e); + if (s) { + var r = Object.getOwnPropertyDescriptor(e, t); + if (r && !r.configurable) { + return false; + } + } + return delete e[t]; + }, + has: function has(e, t) { + In(e); + return t in e; + }, + }; + if (Object.getOwnPropertyNames) { + Object.assign(En, { + ownKeys: function ownKeys(e) { + In(e); + var t = Object.getOwnPropertyNames(e); + if (ce.IsCallable(Object.getOwnPropertySymbols)) { + x(t, Object.getOwnPropertySymbols(e)); + } + return t; + }, + }); + } + var Pn = function ConvertExceptionToBoolean(e) { + return !i(e); + }; + if (Object.preventExtensions) { + Object.assign(En, { + isExtensible: function isExtensible(e) { + In(e); + return Object.isExtensible(e); + }, + preventExtensions: function preventExtensions(e) { + In(e); + return Pn(function () { + return Object.preventExtensions(e); + }); + }, + }); + } + if (s) { + var Cn = function get(e, t, r) { + var n = Object.getOwnPropertyDescriptor(e, t); + if (!n) { + var o = Object.getPrototypeOf(e); + if (o === null) { + return void 0; + } + return Cn(o, t, r); + } + if ('value' in n) { + return n.value; + } + if (n.get) { + return ce.Call(n.get, r); + } + return void 0; + }; + var Mn = function set(e, r, n, o) { + var i = Object.getOwnPropertyDescriptor(e, r); + if (!i) { + var a = Object.getPrototypeOf(e); + if (a !== null) { + return Mn(a, r, n, o); + } + i = { + value: void 0, + writable: true, + enumerable: true, + configurable: true, + }; + } + if ('value' in i) { + if (!i.writable) { + return false; + } + if (!ce.TypeIsObject(o)) { + return false; + } + var u = Object.getOwnPropertyDescriptor(o, r); + if (u) { + return ae.defineProperty(o, r, { value: n }); + } else { + return ae.defineProperty(o, r, { + value: n, + writable: true, + enumerable: true, + configurable: true, + }); + } + } + if (i.set) { + t(i.set, o, n); + return true; + } + return false; + }; + Object.assign(En, { + defineProperty: function defineProperty(e, t, r) { + In(e); + return Pn(function () { + return Object.defineProperty(e, t, r); + }); + }, + getOwnPropertyDescriptor: function getOwnPropertyDescriptor(e, t) { + In(e); + return Object.getOwnPropertyDescriptor(e, t); + }, + get: function get(e, t) { + In(e); + var r = arguments.length > 2 ? arguments[2] : e; + return Cn(e, t, r); + }, + set: function set(e, t, r) { + In(e); + var n = arguments.length > 3 ? arguments[3] : e; + return Mn(e, t, r, n); + }, + }); + } + if (Object.getPrototypeOf) { + var xn = Object.getPrototypeOf; + En.getPrototypeOf = function getPrototypeOf(e) { + In(e); + return xn(e); + }; + } + if (Object.setPrototypeOf && En.getPrototypeOf) { + var Nn = function (e, t) { + var r = t; + while (r) { + if (e === r) { + return true; + } + r = En.getPrototypeOf(r); + } + return false; + }; + Object.assign(En, { + setPrototypeOf: function setPrototypeOf(e, t) { + In(e); + if (t !== null && !ce.TypeIsObject(t)) { + throw new TypeError('proto must be an object or null'); + } + if (t === ae.getPrototypeOf(e)) { + return true; + } + if (ae.isExtensible && !ae.isExtensible(e)) { + return false; + } + if (Nn(e, t)) { + return false; + } + Object.setPrototypeOf(e, t); + return true; + }, + }); + } + var An = function (e, t) { + if (!ce.IsCallable(S.Reflect[e])) { + h(S.Reflect, e, t); + } else { + var r = a(function () { + S.Reflect[e](1); + S.Reflect[e](NaN); + S.Reflect[e](true); + return true; + }); + if (r) { + ne(S.Reflect, e, t); + } + } + }; + Object.keys(En).forEach(function (e) { + An(e, En[e]); + }); + var Rn = S.Reflect.getPrototypeOf; + if (c && Rn && Rn.name !== 'getPrototypeOf') { + ne(S.Reflect, 'getPrototypeOf', function getPrototypeOf(e) { + return t(Rn, S.Reflect, e); + }); + } + if (S.Reflect.setPrototypeOf) { + if ( + a(function () { + S.Reflect.setPrototypeOf(1, {}); + return true; + }) + ) { + ne(S.Reflect, 'setPrototypeOf', En.setPrototypeOf); + } + } + if (S.Reflect.defineProperty) { + if ( + !a(function () { + var e = !S.Reflect.defineProperty(1, 'test', { value: 1 }); + var t = + typeof Object.preventExtensions !== 'function' || + !S.Reflect.defineProperty(Object.preventExtensions({}), 'test', {}); + return e && t; + }) + ) { + ne(S.Reflect, 'defineProperty', En.defineProperty); + } + } + if (S.Reflect.construct) { + if ( + !a(function () { + var e = function F() {}; + return S.Reflect.construct(function () {}, [], e) instanceof e; + }) + ) { + ne(S.Reflect, 'construct', En.construct); + } + } + if (String(new Date(NaN)) !== 'Invalid Date') { + var _n = Date.prototype.toString; + var kn = function toString() { + var e = +this; + if (e !== e) { + return 'Invalid Date'; + } + return ce.Call(_n, this); + }; + ne(Date.prototype, 'toString', kn); + } + var Ln = { + anchor: function anchor(e) { + return ce.CreateHTML(this, 'a', 'name', e); + }, + big: function big() { + return ce.CreateHTML(this, 'big', '', ''); + }, + blink: function blink() { + return ce.CreateHTML(this, 'blink', '', ''); + }, + bold: function bold() { + return ce.CreateHTML(this, 'b', '', ''); + }, + fixed: function fixed() { + return ce.CreateHTML(this, 'tt', '', ''); + }, + fontcolor: function fontcolor(e) { + return ce.CreateHTML(this, 'font', 'color', e); + }, + fontsize: function fontsize(e) { + return ce.CreateHTML(this, 'font', 'size', e); + }, + italics: function italics() { + return ce.CreateHTML(this, 'i', '', ''); + }, + link: function link(e) { + return ce.CreateHTML(this, 'a', 'href', e); + }, + small: function small() { + return ce.CreateHTML(this, 'small', '', ''); + }, + strike: function strike() { + return ce.CreateHTML(this, 'strike', '', ''); + }, + sub: function sub() { + return ce.CreateHTML(this, 'sub', '', ''); + }, + sup: function sub() { + return ce.CreateHTML(this, 'sup', '', ''); + }, + }; + l(Object.keys(Ln), function (e) { + var r = String.prototype[e]; + var n = false; + if (ce.IsCallable(r)) { + var o = t(r, '', ' " '); + var i = P([], o.match(/"/g)).length; + n = o !== o.toLowerCase() || i > 2; + } else { + n = true; + } + if (n) { + ne(String.prototype, e, Ln[e]); + } + }); + var Fn = (function () { + if (!oe) { + return false; + } + var e = + typeof JSON === 'object' && typeof JSON.stringify === 'function' ? JSON.stringify : null; + if (!e) { + return false; + } + if (typeof e($()) !== 'undefined') { + return true; + } + if (e([$()]) !== '[null]') { + return true; + } + var t = { a: $() }; + t[$()] = true; + if (e(t) !== '{}') { + return true; + } + return false; + })(); + var Dn = a(function () { + if (!oe) { + return true; + } + return JSON.stringify(Object($())) === '{}' && JSON.stringify([Object($())]) === '[{}]'; + }); + if (Fn || !Dn) { + var zn = JSON.stringify; + ne(JSON, 'stringify', function stringify(e) { + if (typeof e === 'symbol') { + return; + } + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o = [e]; + if (!r(n)) { + var i = ce.IsCallable(n) ? n : null; + var a = function (e, r) { + var n = i ? t(i, this, e, r) : r; + if (typeof n !== 'symbol') { + if (re.symbol(n)) { + return Nt({})(n); + } else { + return n; + } + } + }; + o.push(a); + } else { + o.push(n); + } + if (arguments.length > 2) { + o.push(arguments[2]); + } + return zn.apply(this, o); + }); + } + return S; +}); //# sourceMappingURL=es6-shim.map diff --git a/platform/app/public/html-templates/index.html b/platform/app/public/html-templates/index.html index 9ff1c3e6926..eb53fc851c8 100644 --- a/platform/app/public/html-templates/index.html +++ b/platform/app/public/html-templates/index.html @@ -1,4 +1,4 @@ - + @@ -6,16 +6,34 @@ name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - - - - + + + + - - + + - + + - + - - - - + + + - + diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 44c3fd7546c..cadc05e2e8b 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -5,12 +5,7 @@ import i18n from '@ohif/i18n'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; import Compose from './routes/Mode/Compose'; -import { - ServicesManager, - ExtensionManager, - CommandsManager, - HotkeysManager, -} from '@ohif/core'; +import { ServicesManager, ExtensionManager, CommandsManager, HotkeysManager } from '@ohif/core'; import { DialogProvider, Modal, @@ -38,9 +33,7 @@ function App({ config, defaultExtensions, defaultModes }) { const [init, setInit] = useState(null); useEffect(() => { const run = async () => { - appInit(config, defaultExtensions, defaultModes) - .then(setInit) - .catch(console.error); + appInit(config, defaultExtensions, defaultModes).then(setInit).catch(console.error); }; run(); @@ -58,13 +51,7 @@ function App({ config, defaultExtensions, defaultModes }) { // Set appConfig const appConfigState = init.appConfig; - const { - routerBasename, - modes, - dataSources, - oidc, - showStudyList, - } = appConfigState; + const { routerBasename, modes, dataSources, oidc, showStudyList } = appConfigState; const { uiDialogService, @@ -89,8 +76,7 @@ function App({ config, defaultExtensions, defaultModes }) { [DialogProvider, { service: uiDialogService }], [ModalProvider, { service: uiModalService, modal: Modal }], ]; - const CombinedProviders = ({ children }) => - Compose({ components: providers, children }); + const CombinedProviders = ({ children }) => Compose({ components: providers, children }); let authRoutes = null; diff --git a/platform/app/src/__tests__/globalSetup.js b/platform/app/src/__tests__/globalSetup.js index 0fecec0801d..0aae7f58464 100644 --- a/platform/app/src/__tests__/globalSetup.js +++ b/platform/app/src/__tests__/globalSetup.js @@ -2,8 +2,10 @@ const _ = require('lodash'); const originalConsoleError = console.error; // JSDom's CSS Parser has limited support for certain features -// This supresses error warnings caused by it -console.error = function(msg) { - if (_.startsWith(msg, 'Error: Could not parse CSS stylesheet')) return; +// This suppresses error warnings caused by it +console.error = function (msg) { + if (_.startsWith(msg, 'Error: Could not parse CSS stylesheet')) { + return; + } originalConsoleError(msg); }; diff --git a/platform/app/src/appInit.js b/platform/app/src/appInit.js index c6ddbf7ac61..168d05a045c 100644 --- a/platform/app/src/appInit.js +++ b/platform/app/src/appInit.js @@ -76,14 +76,8 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { * Example: [ext1, ext2, ext3] * Example2: [[ext1, config], ext2, [ext3, config]] */ - const loadedExtensions = await loadModules([ - ...defaultExtensions, - ...appConfig.extensions, - ]); - await extensionManager.registerExtensions( - loadedExtensions, - appConfig.dataSources - ); + const loadedExtensions = await loadModules([...defaultExtensions, ...appConfig.extensions]); + await extensionManager.registerExtensions(loadedExtensions, appConfig.dataSources); // TODO: We no longer use `utils.addServer` // TODO: We no longer init webWorkers at app level @@ -93,33 +87,36 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { throw new Error('No modes are defined! Check your app-config.js'); } - const loadedModes = await loadModules([ - ...(appConfig.modes || []), - ...defaultModes, - ]); + const loadedModes = await loadModules([...(appConfig.modes || []), ...defaultModes]); - // This is the name for the loaded istance object + // This is the name for the loaded instance object appConfig.loadedModes = []; const modesById = new Set(); for (let i = 0; i < loadedModes.length; i++) { let mode = loadedModes[i]; - if (!mode) continue; + if (!mode) { + continue; + } const { id } = mode; if (mode.modeFactory) { // If the appConfig contains configuration for this mode, use it. - const modeConfig = - appConfig.modeConfig && appConfig.modeConfig[i] - ? appConfig.modeConfig[id] + const modeConfiguration = + appConfig.modesConfiguration && appConfig.modesConfiguration[id] + ? appConfig.modesConfiguration[id] : {}; - mode = mode.modeFactory(modeConfig); + mode = mode.modeFactory({ modeConfiguration }); } - if (modesById.has(id)) continue; + if (modesById.has(id)) { + continue; + } // Prevent duplication modesById.add(id); - if (!mode || typeof mode !== 'object') continue; + if (!mode || typeof mode !== 'object') { + continue; + } appConfig.loadedModes.push(mode); } // Hack alert - don't touch the original modes definition, diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx index 67d3280412c..e6e1c239470 100644 --- a/platform/app/src/components/ViewportGrid.tsx +++ b/platform/app/src/components/ViewportGrid.tsx @@ -5,21 +5,17 @@ import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui'; import EmptyViewport from './EmptyViewport'; import classNames from 'classnames'; - function ViewerViewportGrid(props) { const { servicesManager, viewportComponents, dataSource } = props; const [viewportGrid, viewportGridService] = useViewportGrid(); - const { layout, activeViewportIndex, viewports } = viewportGrid; + const { layout, activeViewportId, viewports } = viewportGrid; const { numCols, numRows } = layout; // TODO -> Need some way of selecting which displaySets hit the viewports. - const { - displaySetService, - measurementService, - hangingProtocolService, - uiNotificationService, - } = (servicesManager as ServicesManager).services; + const { displaySetService, measurementService, hangingProtocolService, uiNotificationService } = ( + servicesManager as ServicesManager + ).services; /** * This callback runs after the viewports structure has changed in any way. @@ -51,10 +47,11 @@ function ViewerViewportGrid(props) { * specify the viewport match details, which specifies the size and * setup of the various viewports. */ - const findOrCreateViewport = viewportIndex => { - const details = viewportMatchDetails.get(viewportIndex); + const findOrCreateViewport = pos => { + const viewportId = Array.from(viewportMatchDetails.keys())[pos]; + const details = viewportMatchDetails.get(viewportId); if (!details) { - console.log('No match details for viewport', viewportIndex); + console.log('No match details for viewport', viewportId); return; } @@ -62,15 +59,13 @@ function ViewerViewportGrid(props) { const displaySetUIDsToHang = []; const displaySetUIDsToHangOptions = []; - displaySetsInfo.forEach( - ({ displaySetInstanceUID, displaySetOptions }) => { - if (displaySetInstanceUID) { - displaySetUIDsToHang.push(displaySetInstanceUID); - } - - displaySetUIDsToHangOptions.push(displaySetOptions); + displaySetsInfo.forEach(({ displaySetInstanceUID, displaySetOptions }) => { + if (displaySetInstanceUID) { + displaySetUIDsToHang.push(displaySetInstanceUID); } - ); + + displaySetUIDsToHangOptions.push(displaySetOptions); + }); const computedViewportOptions = hangingProtocolService.getComputedOptions( viewportOptions, @@ -99,11 +94,11 @@ function ViewerViewportGrid(props) { }; const _getUpdatedViewports = useCallback( - (viewportIndex, displaySetInstanceUID) => { + (viewportId, displaySetInstanceUID) => { let updatedViewports = []; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { @@ -127,12 +122,7 @@ function ViewerViewportGrid(props) { const { unsubscribe } = hangingProtocolService.subscribe( hangingProtocolService.EVENTS.PROTOCOL_CHANGED, ({ protocol, stage, activeStudyUID, viewportMatchDetails }) => { - updateDisplaySetsFromProtocol( - protocol, - stage, - activeStudyUID, - viewportMatchDetails - ); + updateDisplaySetsFromProtocol(protocol, stage, activeStudyUID, viewportMatchDetails); } ); @@ -144,18 +134,16 @@ function ViewerViewportGrid(props) { useEffect(() => { const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, - ({ viewportIndex, measurement, isConsumed }) => { - if (isConsumed) return; + ({ viewportId, measurement, isConsumed }) => { + if (isConsumed) { + return; + } // This occurs when no viewport has elected to consume the event // so we need to change layouts into a layout which can consume // the event. - const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = - measurement; + const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement; - const updatedViewports = _getUpdatedViewports( - viewportIndex, - referencedDisplaySetInstanceUID - ); + const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID); // Arbitrarily assign the viewport to element 0 const viewport = updatedViewports?.[0]; @@ -170,9 +158,7 @@ function ViewerViewportGrid(props) { viewport.viewportOptions ||= {}; viewport.viewportOptions.orientation = 'acquisition'; - const displaySet = displaySetService.getDisplaySetByUID( - referencedDisplaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(referencedDisplaySetInstanceUID); // jump straight to the initial image index if we can if (displaySet.images && measurement.SOPInstanceUID) { for (let index = 0; index < displaySet.images.length; index++) { @@ -195,7 +181,7 @@ function ViewerViewportGrid(props) { }, [viewports]); /** - const onDoubleClick = viewportIndex => { + const onDoubleClick = viewportId => { // TODO -> Disabled for now. // onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport. // Which it really really shouldn't. We need a larger fix for jump to @@ -204,7 +190,7 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: cachedLayout.numCols, numRows: cachedLayout.numRows, - activeViewportIndex: cachedLayout.activeViewportIndex, + activeViewportId: cachedLayout.activeViewportId, viewports: cachedLayout.viewports, cachedLayout: null, }); @@ -221,10 +207,10 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: 1, numRows: 1, - activeViewportIndex: 0, + activeViewportId: 0, viewports: [ { - displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, + displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID, imageIndex: undefined, }, ], @@ -232,17 +218,14 @@ function ViewerViewportGrid(props) { numCols, numRows, viewports: cachedViewports, - activeViewportIndex: viewportIndex, + activeViewportId: viewportId, }, }); }; */ - const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { - const updatedViewports = _getUpdatedViewports( - viewportIndex, - displaySetInstanceUID - ); + const onDropHandler = (viewportId, { displaySetInstanceUID }) => { + const updatedViewports = _getUpdatedViewports(viewportId, displaySetInstanceUID); viewportGridService.setDisplaySetsForViewports(updatedViewports); }; @@ -251,13 +234,7 @@ function ViewerViewportGrid(props) { const numViewportPanes = viewportGridService.getNumViewportPanes(); for (let i = 0; i < numViewportPanes; i++) { - const viewportIndex = i; - const isActive = activeViewportIndex === viewportIndex; - const paneMetadata = viewports[i] || {}; - const viewportId = paneMetadata.viewportId || `viewport-${i}`; - if (!paneMetadata.viewportId) { - paneMetadata.viewportId = viewportId; - } + const paneMetadata = Array.from(viewports.values())[i] || {}; const { displaySetInstanceUIDs, viewportOptions, @@ -269,16 +246,19 @@ function ViewerViewportGrid(props) { viewportLabel, } = paneMetadata; + const viewportId = viewportOptions.viewportId; + const isActive = activeViewportId === viewportId; + const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || []; - // This is causing the viewport components re-render when the activeViewportIndex changes - const displaySets = displaySetInstanceUIDsToUse.map( - displaySetInstanceUID => { - return ( - displaySetService.getDisplaySetByUID(displaySetInstanceUID) || {} - ); - } - ); + // This is causing the viewport components re-render when the activeViewportId changes + const displaySets = displaySetInstanceUIDsToUse + .map(displaySetInstanceUID => { + return displaySetService.getDisplaySetByUID(displaySetInstanceUID) || {}; + }) + .filter(displaySet => { + return !displaySet?.unsupported; + }); const ViewportComponent = _getViewportComponent( displaySets, @@ -292,24 +272,32 @@ function ViewerViewportGrid(props) { }); const onInteractionHandler = event => { - if (isActive) return; + if (isActive) { + return; + } if (event) { event.preventDefault(); event.stopPropagation(); } - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }; - // TEMP -> Double click disabled for now - // onDoubleClick={() => onDoubleClick(viewportIndex)} - viewportPanes[i] = (
1 ? viewportLabel : ''} + viewportLabel={viewports.size > 1 ? viewportLabel : ''} + viewportId={viewportId} dataSource={dataSource} viewportOptions={viewportOptions} displaySetOptions={displaySetOptions} @@ -341,7 +329,7 @@ function ViewerViewportGrid(props) { } return viewportPanes; - }, [viewports, activeViewportIndex, viewportComponents, dataSource]); + }, [viewports, activeViewportId, viewportComponents, dataSource]); /** * Loading indicator until numCols and numRows are gotten from the HangingProtocolService @@ -351,7 +339,10 @@ function ViewerViewportGrid(props) { } return ( - + {/* {ViewportPanes} */} {getViewportPanes()} @@ -367,11 +358,7 @@ ViewerViewportGrid.defaultProps = { viewportComponents: [], }; -function _getViewportComponent( - displaySets, - viewportComponents, - uiNotificationService -) { +function _getViewportComponent(displaySets, viewportComponents, uiNotificationService) { if (!displaySets || !displaySets.length) { return EmptyViewport; } @@ -386,9 +373,7 @@ function _getViewportComponent( if (!viewportComponents[i].displaySetsToDisplay) { throw new Error('displaySetsToDisplay is null'); } - if ( - viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId) - ) { + if (viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)) { const { component } = viewportComponents[i]; return component; } @@ -397,7 +382,7 @@ function _getViewportComponent( console.log("Can't show displaySet", SOPClassHandlerId, displaySets[0]); uiNotificationService.show({ title: 'Viewport Not Supported Yet', - message: `Cannot display SOPClassId of ${displaySets[0].SOPClassUID} yet`, + message: `Cannot display SOPClassUID of ${displaySets[0].SOPClassUID} yet`, type: 'error', }); diff --git a/platform/app/src/hooks/index.js b/platform/app/src/hooks/index.js index 0660f2611f2..b2e97562e12 100644 --- a/platform/app/src/hooks/index.js +++ b/platform/app/src/hooks/index.js @@ -1,5 +1,4 @@ import useDebounce from './useDebounce.js'; -import useQuery from './useQuery.js'; -import useSearchParams from './useSearchParams.js'; +import useSearchParams from './useSearchParams'; -export { useDebounce, useQuery, useSearchParams }; +export { useDebounce, useSearchParams }; diff --git a/platform/app/src/hooks/useQuery.js b/platform/app/src/hooks/useQuery.js deleted file mode 100644 index 53cd6659547..00000000000 --- a/platform/app/src/hooks/useQuery.js +++ /dev/null @@ -1,5 +0,0 @@ -import { useLocation } from 'react-router-dom'; - -export default function useQuery() { - return new URLSearchParams(useLocation().search); -} diff --git a/platform/app/src/hooks/useSearchParams.js b/platform/app/src/hooks/useSearchParams.js deleted file mode 100644 index c7cd0aee848..00000000000 --- a/platform/app/src/hooks/useSearchParams.js +++ /dev/null @@ -1,17 +0,0 @@ -import useQuery from './useQuery'; - -/** - * It returns a Map of the query parameters in the URL, where the keys are - * lowercase - * @returns A function that returns a Map of the query parameters. - */ -export default function useSearchParams() { - const query = useQuery(); - // make query params case-insensitive - const searchParams = new Map(); - for (const [key, value] of query) { - searchParams.set(key.toLowerCase(), value); - } - - return searchParams; -} diff --git a/platform/app/src/hooks/useSearchParams.ts b/platform/app/src/hooks/useSearchParams.ts new file mode 100644 index 00000000000..3ceaebd9de8 --- /dev/null +++ b/platform/app/src/hooks/useSearchParams.ts @@ -0,0 +1,24 @@ +import { useLocation } from 'react-router'; + +/** + * It returns a URLSearchParams of the query parameters in the URL, where the keys are + * either lowercase or maintain their case based on the lowerCaseKeys parameter. + * @param {lowerCaseKeys:boolean} true to return lower case keys; false (default) to maintain casing; + * @returns {URLSearchParams} + */ +export default function useSearchParams(options = { lowerCaseKeys: false }) { + const { lowerCaseKeys } = options; + const searchParams = new URLSearchParams(useLocation().search); + + if (!lowerCaseKeys) { + return searchParams; + } + + const lowerCaseSearchParams = new URLSearchParams(); + + for (const [key, value] of searchParams) { + lowerCaseSearchParams.set(key.toLowerCase(), value); + } + + return lowerCaseSearchParams; +} diff --git a/platform/app/src/index.js b/platform/app/src/index.js index 6a6c064bf6c..7b8d63324da 100644 --- a/platform/app/src/index.js +++ b/platform/app/src/index.js @@ -16,10 +16,7 @@ import { history } from './utils/history'; * pluginImports.js imports all of the modes and extensions and adds them * to the window for processing. */ -import { - modes as defaultModes, - extensions as defaultExtensions, -} from './pluginImports'; +import { modes as defaultModes, extensions as defaultExtensions } from './pluginImports'; import loadDynamicConfig from './loadDynamicConfig'; loadDynamicConfig(window.config).then(config_json => { diff --git a/platform/app/src/routes/DataSourceWrapper.tsx b/platform/app/src/routes/DataSourceWrapper.tsx index 026bba14c50..c9d1d693692 100644 --- a/platform/app/src/routes/DataSourceWrapper.tsx +++ b/platform/app/src/routes/DataSourceWrapper.tsx @@ -1,10 +1,25 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { MODULE_TYPES } from '@ohif/core'; +import { ExtensionManager, MODULE_TYPES, Types, log } from '@ohif/core'; // import { extensionManager } from '../App.tsx'; import { useParams, useLocation } from 'react-router'; +import { useNavigate } from 'react-router-dom'; +import useSearchParams from '../hooks/useSearchParams.ts'; + +const { TimingEnum } = Types; + +/** + * Determines if two React Router location objects are the same. + */ +const areLocationsTheSame = (location0, location1) => { + return ( + location0.pathname === location1.pathname && + location0.search === location1.search && + location0.hash === location1.hash + ); +}; /** * Uses route properties to determine the data source that should be passed @@ -15,40 +30,12 @@ import { useParams, useLocation } from 'react-router'; * @param {function} props.children - Layout Template React Component */ function DataSourceWrapper(props) { + const navigate = useNavigate(); const { children: LayoutTemplate, ...rest } = props; const params = useParams(); const location = useLocation(); - - // TODO - get the variable from the props all the time... - let dataSourceName = new URLSearchParams(location.search).get('datasources'); - const dataPath = dataSourceName ? `/${dataSourceName}` : ''; - - if (!dataSourceName && window.config.defaultDataSourceName) { - dataSourceName = window.config.defaultDataSourceName; - } else if (!dataSourceName) { - // Gets the first defined datasource with the right name - // Mostly for historical reasons - new configs should use the defaultDataSourceName - const dataSourceModules = - extensionManager.modules[MODULE_TYPES.DATA_SOURCE]; - // TODO: Good usecase for flatmap? - const webApiDataSources = dataSourceModules.reduce((acc, curr) => { - const mods = []; - curr.module.forEach(mod => { - if (mod.type === 'webApi') { - mods.push(mod); - } - }); - return acc.concat(mods); - }, []); - dataSourceName = webApiDataSources - .map(ds => ds.name) - .find(it => extensionManager.getDataSources(it)?.[0] !== undefined); - } - const dataSource = extensionManager.getDataSources(dataSourceName)?.[0]; - if (!dataSource) { - throw new Error(`No data source found for ${dataSourceName}`); - } - + const lowerCaseSearchParams = useSearchParams({ lowerCaseKeys: true }); + const query = useSearchParams(); // Route props --> studies.mapParams // mapParams --> studies.search // studies.search --> studies.processResults @@ -63,18 +50,108 @@ function DataSourceWrapper(props) { pageNumber: 1, location: 'Not a valid location, causes first load to occur', }; + + const getInitialDataSourceName = useCallback(() => { + // TODO - get the variable from the props all the time... + let dataSourceName = lowerCaseSearchParams.get('datasources'); + + if (!dataSourceName && window.config.defaultDataSourceName) { + return ''; + } + + if (!dataSourceName) { + // Gets the first defined datasource with the right name + // Mostly for historical reasons - new configs should use the defaultDataSourceName + const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE]; + // TODO: Good usecase for flatmap? + const webApiDataSources = dataSourceModules.reduce((acc, curr) => { + const mods = []; + curr.module.forEach(mod => { + if (mod.type === 'webApi') { + mods.push(mod); + } + }); + return acc.concat(mods); + }, []); + dataSourceName = webApiDataSources + .map(ds => ds.name) + .find(it => extensionManager.getDataSources(it)?.[0] !== undefined); + } + + return dataSourceName; + }, []); + + const [isDataSourceInitialized, setIsDataSourceInitialized] = useState(false); + + // The path to the data source to be used in the URL for a mode (e.g. mode/dataSourcePath?StudyIntanceUIDs=1.2.3) + const [dataSourcePath, setDataSourcePath] = useState(() => { + const dataSourceName = getInitialDataSourceName(); + return dataSourceName ? `/${dataSourceName}` : ''; + }); + + const [dataSource, setDataSource] = useState(() => { + const dataSourceName = getInitialDataSourceName(); + + if (!dataSourceName) { + return extensionManager.getActiveDataSource()[0]; + } + + const dataSource = extensionManager.getDataSources(dataSourceName)?.[0]; + if (!dataSource) { + throw new Error(`No data source found for ${dataSourceName}`); + } + + return dataSource; + }); + const [data, setData] = useState(DEFAULT_DATA); const [isLoading, setIsLoading] = useState(false); + /** + * The effect to initialize the data source whenever it changes. Similar to + * whenever a different Mode is entered, the Mode's data source is initialized, so + * too this DataSourceWrapper must initialize its data source whenever a different + * data source is activated. Furthermore, a data source might be initialized + * several times as it gets activated/deactivated because the location URL + * might change and data sources initialize based on the URL. + */ useEffect(() => { - const queryFilterValues = _getQueryFilterValues( - location.search, - STUDIES_LIMIT + const initializeDataSource = async () => { + await dataSource.initialize({ params, query }); + setIsDataSourceInitialized(true); + }; + + initializeDataSource(); + }, [dataSource]); + + useEffect(() => { + const dataSourceChangedCallback = () => { + setIsLoading(false); + setIsDataSourceInitialized(false); + setDataSourcePath(''); + setDataSource(extensionManager.getActiveDataSource()[0]); + // Setting data to DEFAULT_DATA triggers a new query just like it does for the initial load. + setData(DEFAULT_DATA); + }; + + const sub = extensionManager.subscribe( + ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, + dataSourceChangedCallback ); + return () => sub.unsubscribe(); + }, []); + + useEffect(() => { + if (!isDataSourceInitialized) { + return; + } + + const queryFilterValues = _getQueryFilterValues(location.search, STUDIES_LIMIT); // 204: no content async function getData() { setIsLoading(true); + log.time(TimingEnum.SEARCH_TO_LIST); const studies = await dataSource.query.studies.search(queryFilterValues); setData({ @@ -84,6 +161,8 @@ function DataSourceWrapper(props) { pageNumber: queryFilterValues.pageNumber, location, }); + log.timeEnd(TimingEnum.SCRIPT_TO_VIEW); + log.timeEnd(TimingEnum.SEARCH_TO_LIST); setIsLoading(false); } @@ -94,27 +173,38 @@ function DataSourceWrapper(props) { // - And we didn't cross a result offset range const isSamePage = data.pageNumber === queryFilterValues.pageNumber; const previousOffset = - Math.floor((data.pageNumber * data.resultsPerPage) / STUDIES_LIMIT) * - (STUDIES_LIMIT - 1); + Math.floor((data.pageNumber * data.resultsPerPage) / STUDIES_LIMIT) * (STUDIES_LIMIT - 1); const newOffset = Math.floor( - (queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) / - STUDIES_LIMIT + (queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) / STUDIES_LIMIT ) * (STUDIES_LIMIT - 1); - const isLocationUpdated = data.location !== location; + // Simply checking data.location !== location is not sufficient because even though the location href (i.e. entire URL) + // has not changed, the React Router still provides a new location reference and would result in two study queries + // on initial load. Alternatively, window.location.href could be used. + const isLocationUpdated = + typeof data.location === 'string' || !areLocationsTheSame(data.location, location); const isDataInvalid = - !isSamePage || - (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); + !isSamePage || (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); if (isDataInvalid) { - getData(); + getData().catch(e => { + console.error(e); + // If there is a data source configuration API, then the Worklist will popup the dialog to attempt to configure it + // and attempt to resolve this issue. + if (dataSource.getConfig().configurationAPI) { + return; + } + + // No data source configuration API, so navigate to the not found server page. + navigate('/notfoundserver', '_self'); + }); } } catch (ex) { console.warn(ex); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, location, params, isLoading, setIsLoading]); + }, [data, location, params, isLoading, setIsLoading, dataSource, isDataSourceInitialized]); // queryFilterValues // TODO: Better way to pass DataSource? @@ -122,7 +212,7 @@ function DataSourceWrapper(props) { -
-
+
+
OHIF -
-
-

- Debug Information -

-
-

- Cross Origin Isolated (COOP/COEP) -

+
+
+

Debug Information

+
+

Cross Origin Isolated (COOP/COEP)

{!window.crossOriginIsolated && ( -
- We use SharedArrayBuffer to render volume data (e.g., MPR). - If you are seeing this message, it means that your browser - has not enabled COOP/COEP. Please see the following link for - more information:{' '} +
+ We use SharedArrayBuffer to render volume data (e.g., MPR). If you are seeing + this message, it means that your browser has not enabled COOP/COEP. Please see + the following link for more information:{' '} { return ( - + {({ getRootProps, getInputProps }) => (
- - ); - })} +
+ {appConfig.loadedModes.map((mode, i) => { + const modalitiesToCheck = modalities.replaceAll('/', '\\'); + + const isValidMode = mode.isValidMode({ + modalities: modalitiesToCheck, + study, + }); + // TODO: Modes need a default/target route? We mostly support a single one for now. + // We should also be using the route path, but currently are not + // mode.routeName + // mode.routes[x].path + // Don't specify default data source, and it should just be picked up... (this may not currently be the case) + // How do we know which params to pass? Today, it's just StudyInstanceUIDs and configUrl if exists + const query = new URLSearchParams(); + if (filterValues.configUrl) { + query.append('configUrl', filterValues.configUrl); + } + query.append('StudyInstanceUIDs', studyInstanceUid); + return ( + mode.displayName && ( + { + // In case any event bubbles up for an invalid mode, prevent the navigation. + // For example, the event bubbles up when the icon embedded in the disabled button is clicked. + if (!isValidMode) { + event.preventDefault(); + } + }} + // to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`} + > + {/* TODO revisit the completely rounded style of buttons used for launching a mode from the worklist later - for now use LegacyButton*/} + } // launch-arrow | launch-info + onClick={() => {}} + > + {t(`Modes:${mode.displayName}`)} + + + ) + ); + })} +
), onClickRow: () => - setExpandedRows(s => - isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey] - ), + setExpandedRows(s => (isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey])), isExpanded, }; }); @@ -409,16 +416,16 @@ function WorkList({ title: t('UserPreferencesModal:User Preferences'), content: UserPreferences, contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( - hotkeyDefaults - ), + hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), hotkeyDefinitions, onCancel: hide, currentLanguage: currentLanguage(), availableLanguages, defaultLanguage, onSubmit: state => { - i18n.changeLanguage(state.language.value); + if (state.language.value !== currentLanguage().value) { + i18n.changeLanguage(state.language.value); + } hotkeysManager.setHotkeys(state.hotkeyDefinitions); hide(); }, @@ -434,9 +441,7 @@ function WorkList({ icon: 'power-off', title: t('Header:Logout'), onClick: () => { - navigate( - `/logout?redirect_uri=${encodeURIComponent(window.location.href)}` - ); + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); }, }); } @@ -445,7 +450,7 @@ function WorkList({ const { component: dicomUploadComponent } = customizationService.get('dicomUploadComponent') ?? {}; const uploadProps = - dicomUploadComponent && dataSource.getConfig().dicomUploadEnabled + dicomUploadComponent && dataSource.getConfig()?.dicomUploadEnabled ? { title: 'Upload files', closeButton: true, @@ -468,15 +473,18 @@ function WorkList({ } : undefined; + const { component: dataSourceConfigurationComponent } = + customizationService.get('ohif.dataSourceConfigurationComponent') ?? {}; + return ( -
+
-
+
100 ? 101 : numOfStudies} filtersMeta={filtersMeta} @@ -485,12 +493,16 @@ function WorkList({ clearFilters={() => setFilterValues(defaultFilterValues)} isFiltering={isFiltering(filterValues, defaultFilterValues)} onUploadClick={uploadProps ? () => show(uploadProps) : undefined} + getDataSourceConfigurationComponent={ + dataSourceConfigurationComponent ? () => dataSourceConfigurationComponent() : undefined + } /> {hasStudies ? ( -
+
@@ -505,7 +517,7 @@ function WorkList({ ) : (
{appConfig.showLoadingIndicator && isLoadingData ? ( - + ) : ( )} @@ -563,9 +575,7 @@ function _getQueryFilterValues(params) { endDate: params.get('enddate') || null, }, description: params.get('description'), - modalities: params.get('modalities') - ? params.get('modalities').split(',') - : [], + modalities: params.get('modalities') ? params.get('modalities').split(',') : [], accession: params.get('accession'), sortBy: params.get('sortby'), sortDirection: params.get('sortdirection'), @@ -589,9 +599,7 @@ function _sortStringDates(s1, s2, sortModifier) { const s2Date = moment(s2.date, ['YYYYMMDD', 'YYYY.MM.DD'], true); if (s1Date.isValid() && s2Date.isValid()) { - return ( - (s1Date.toISOString() > s2Date.toISOString() ? 1 : -1) * sortModifier - ); + return (s1Date.toISOString() > s2Date.toISOString() ? 1 : -1) * sortModifier; } else if (s1Date.isValid()) { return sortModifier; } else if (s2Date.isValid()) { diff --git a/platform/app/src/routes/buildModeRoutes.tsx b/platform/app/src/routes/buildModeRoutes.tsx index c3375788d5e..be46177b68f 100644 --- a/platform/app/src/routes/buildModeRoutes.tsx +++ b/platform/app/src/routes/buildModeRoutes.tsx @@ -63,16 +63,14 @@ export default function buildModeRoutes({ }); }); - const defaultDataSourceName = extensionManager.defaultDataSourceName; - - // Add default DataSource route. + // Add active DataSource route. + // This is the DataSource route for the active data source defined in ExtensionManager.getActiveDataSource const path = `/${mode.routeName}`; // TODO move up. const children = () => ( { + return ( +
+
+

{message}

+
+
+ ); +}; + +NotFoundServer.propTypes = { + message: PropTypes.string, +}; + +const NotFoundStudy = () => { + return ( +
+
+

+ One or more of the requested studies are not available at this time. Return to the{' '} + + study list + {' '} + to select a different study to view. +

+
+
+ ); +}; + +NotFoundStudy.propTypes = { + message: PropTypes.string, +}; // TODO: Include "routes" debug route if dev build const bakedInRoutes = [ + { + path: '/notfoundserver', + children: NotFoundServer, + }, + { + path: '/notfoundstudy', + children: NotFoundStudy, + }, { path: '/debug', children: Debug, @@ -56,12 +105,10 @@ const createRoutes = ({ path: '/', children: DataSourceWrapper, private: true, - props: { children: WorkList, servicesManager }, + props: { children: WorkList, servicesManager, extensionManager }, }; - const customRoutes = customizationService.getGlobalCustomization( - 'customRoutes' - ); + const customRoutes = customizationService.getGlobalCustomization('customRoutes'); const allRoutes = [ ...routes, ...(showStudyList ? [WorkListRoute] : []), @@ -73,7 +120,10 @@ const createRoutes = ({ function RouteWithErrorBoundary({ route, ...rest }) { // eslint-disable-next-line react/jsx-props-no-spreading return ( - + + } diff --git a/platform/app/src/service-worker.js b/platform/app/src/service-worker.js index e97198b4101..a235b8c7924 100644 --- a/platform/app/src/service-worker.js +++ b/platform/app/src/service-worker.js @@ -1,13 +1,11 @@ -navigator.serviceWorker.getRegistrations().then(function(registrations) { +navigator.serviceWorker.getRegistrations().then(function (registrations) { for (let registration of registrations) { registration.unregister(); } }); // https://developers.google.com/web/tools/workbox/guides/troubleshoot-and-debug -importScripts( - 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-sw.js' -); +importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-sw.js'); // Install newest // https://developers.google.com/web/tools/workbox/modules/workbox-core diff --git a/platform/app/src/utils/OpenIdConnectRoutes.tsx b/platform/app/src/utils/OpenIdConnectRoutes.tsx index 861bd770bb4..4552b0527eb 100644 --- a/platform/app/src/utils/OpenIdConnectRoutes.tsx +++ b/platform/app/src/utils/OpenIdConnectRoutes.tsx @@ -34,18 +34,13 @@ const initUserManager = (oidc, routerBasename) => { const baseUri = `${protocol}//${host}${routerBasename}`; const redirect_uri = firstOpenIdClient.redirect_uri || '/callback'; - const silent_redirect_uri = - firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html'; - const post_logout_redirect_uri = - firstOpenIdClient.post_logout_redirect_uri || '/'; + const silent_redirect_uri = firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html'; + const post_logout_redirect_uri = firstOpenIdClient.post_logout_redirect_uri || '/'; const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, { redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri), silent_redirect_uri: _makeAbsoluteIfNecessary(silent_redirect_uri, baseUri), - post_logout_redirect_uri: _makeAbsoluteIfNecessary( - post_logout_redirect_uri, - baseUri - ), + post_logout_redirect_uri: _makeAbsoluteIfNecessary(post_logout_redirect_uri, baseUri), }); return getUserManagerForOpenIdConnectClient(openIdConnectConfiguration); @@ -77,18 +72,12 @@ function LoginComponent(userManager) { const ohifRedirectTo = { pathname: new URL(targetLinkUri).pathname, }; - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify(ohifRedirectTo) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify(ohifRedirectTo)); } else { const ohifRedirectTo = { pathname: '/', }; - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify(ohifRedirectTo) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify(ohifRedirectTo)); } if (loginHint !== null) { @@ -101,23 +90,25 @@ function LoginComponent(userManager) { return null; } -function OpenIdConnectRoutes({ - oidc, - routerBasename, - userAuthenticationService, -}) { +function OpenIdConnectRoutes({ oidc, routerBasename, userAuthenticationService }) { const userManager = initUserManager(oidc, routerBasename); const getAuthorizationHeader = () => { const user = userAuthenticationService.getUser(); + // if the user is null return early, next time + // we hit this function we will have a user + if (!user) { + return; + } + return { Authorization: `Bearer ${user.access_token}`, }; }; - const handleUnauthenticated = () => { - userManager.signinRedirect(); + const handleUnauthenticated = async () => { + await userManager.signinRedirect(); // return null because this is used in a react component return null; @@ -131,9 +122,7 @@ function OpenIdConnectRoutes({ const storageEventListener = event => { const signOutEvent = localStorage.getItem('signoutEvent'); if (signOutEvent) { - navigate( - `/logout?redirect_uri=${encodeURIComponent(window.location.href)}` - ); + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); } }; @@ -159,24 +148,21 @@ function OpenIdConnectRoutes({ const { pathname, search } = location; const redirect_uri = new URL(userManager.settings._redirect_uri).pathname; //.replace(routerBasename,'') - const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri) - .pathname; //.replace(routerBasename,'') - const post_logout_redirect_uri = new URL( - userManager.settings._post_logout_redirect_uri - ).pathname; //.replace(routerBasename,''); + const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri).pathname; //.replace(routerBasename,'') + const post_logout_redirect_uri = new URL(userManager.settings._post_logout_redirect_uri).pathname; //.replace(routerBasename,''); // const pathnameRelative = pathname.replace(routerBasename,''); if (pathname !== redirect_uri) { - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify({ pathname, search }) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify({ pathname, search })); } return ( - + ({ ...theme('spacing'), - '0': '0', + 0: '0', auto: 'auto', full: '100%', viewport: '0.5rem', @@ -329,7 +325,7 @@ module.exports = { }), minHeight: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', full: '100%', screen: '100vh', }), @@ -348,14 +344,14 @@ module.exports = { normal: '1.5', relaxed: '1.625', loose: '2', - '3': '.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '7': '1.75rem', - '8': '2rem', - '9': '2.25rem', - '10': '2.5rem', + 3: '.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', }, listStyleType: { none: 'none', @@ -390,7 +386,7 @@ module.exports = { }), minWidth: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', xs: '2rem', sm: '4rem', md: '6rem', @@ -410,44 +406,44 @@ module.exports = { top: 'top', }, opacity: { - '0': '0', - '5': '.5', - '10': '.10', - '15': '.15', - '20': '.20', - '25': '.25', - '30': '.30', - '35': '.35', - '40': '.40', - '45': '.45', - '50': '.50', - '55': '.55', - '60': '.60', - '65': '.65', - '70': '.70', - '75': '.75', - '80': '.80', - '85': '.85', - '90': '.90', - '95': '.95', - '100': '1', + 0: '0', + 5: '.5', + 10: '.10', + 15: '.15', + 20: '.20', + 25: '.25', + 30: '.30', + 35: '.35', + 40: '.40', + 45: '.45', + 50: '.50', + 55: '.55', + 60: '.60', + 65: '.65', + 70: '.70', + 75: '.75', + 80: '.80', + 85: '.85', + 90: '.90', + 95: '.95', + 100: '1', }, order: { first: '-9999', last: '9999', none: '0', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', }, padding: theme => theme('spacing'), placeholderColor: theme => theme('colors'), @@ -456,9 +452,9 @@ module.exports = { current: 'currentColor', }), strokeWidth: { - '0': '0', - '1': '1', - '2': '2', + 0: '0', + 1: '1', + 2: '2', }, textColor: theme => theme('colors'), width: theme => ({ @@ -519,28 +515,28 @@ module.exports = { }), zIndex: { auto: 'auto', - '0': '0', - '10': '10', - '20': '20', - '30': '30', - '40': '40', - '50': '50', + 0: '0', + 10: '10', + 20: '20', + 30: '30', + 40: '40', + 50: '50', }, gap: theme => theme('spacing'), gridTemplateColumns: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', - '7': 'repeat(7, minmax(0, 1fr))', - '8': 'repeat(8, minmax(0, 1fr))', - '9': 'repeat(9, minmax(0, 1fr))', - '10': 'repeat(10, minmax(0, 1fr))', - '11': 'repeat(11, minmax(0, 1fr))', - '12': 'repeat(12, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + 7: 'repeat(7, minmax(0, 1fr))', + 8: 'repeat(8, minmax(0, 1fr))', + 9: 'repeat(9, minmax(0, 1fr))', + 10: 'repeat(10, minmax(0, 1fr))', + 11: 'repeat(11, minmax(0, 1fr))', + 12: 'repeat(12, minmax(0, 1fr))', }, gridColumn: { auto: 'auto', @@ -559,44 +555,44 @@ module.exports = { }, gridColumnStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridColumnEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridTemplateRows: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', }, gridRow: { auto: 'auto', @@ -609,23 +605,23 @@ module.exports = { }, gridRowStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, gridRowEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, transformOrigin: { center: 'center', @@ -639,25 +635,25 @@ module.exports = { 'top-left': 'top left', }, scale: { - '0': '0', - '50': '.5', - '75': '.75', - '90': '.9', - '95': '.95', - '100': '1', - '105': '1.05', - '110': '1.1', - '125': '1.25', - '150': '1.5', + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', - '0': '0', - '45': '45deg', - '90': '90deg', - '180': '180deg', + 0: '0', + 45: '45deg', + 90: '90deg', + 180: '180deg', }, translate: (theme, { negative }) => ({ ...theme('spacing'), @@ -671,10 +667,10 @@ module.exports = { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', - '0': '0', - '3': '3deg', - '6': '6deg', - '12': '12deg', + 0: '0', + 3: '3deg', + 6: '6deg', + 12: '12deg', }, transitionProperty: { none: 'none', @@ -694,14 +690,14 @@ module.exports = { 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, transitionDuration: { - '75': '75ms', - '100': '100ms', - '150': '150ms', - '200': '200ms', - '300': '300ms', - '500': '500ms', - '700': '700ms', - '1000': '1000ms', + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', }, }, variants: { @@ -711,26 +707,12 @@ module.exports = { alignSelf: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], - backgroundColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], - borderColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], borderRadius: ['responsive', 'focus', 'first', 'last'], borderStyle: ['responsive', 'focus'], borderWidth: ['responsive', 'focus', 'first', 'last'], diff --git a/platform/cli/CHANGELOG.md b/platform/cli/CHANGELOG.md new file mode 100644 index 00000000000..46b2b8000fa --- /dev/null +++ b/platform/cli/CHANGELOG.md @@ -0,0 +1,428 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + + +### Bug Fixes + +* **cli:** Add npm packaged mode not working ([#3689](https://github.com/OHIF/Viewers/issues/3689)) ([28cec04](https://github.com/OHIF/Viewers/commit/28cec04ff43b81e218c3e9addef4665b3833a6fe)) + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + + +### Bug Fixes + +* **cli:** various fixes for adding custom modes and extensions ([#3683](https://github.com/OHIF/Viewers/issues/3683)) ([dc73b18](https://github.com/OHIF/Viewers/commit/dc73b187484da029a2664bb1302f30137c973b8c)) + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/cli diff --git a/platform/cli/package.json b/platform/cli/package.json index a677a87d5cd..3ef8aa00a32 100644 --- a/platform/cli/package.json +++ b/platform/cli/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/cli", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "A CLI to bootstrap new OHIF extension or mode", "type": "module", "main": "src/index.js", diff --git a/platform/cli/src/commands/addExtension.js b/platform/cli/src/commands/addExtension.js index 62ca566efc6..37e2c3d09c6 100644 --- a/platform/cli/src/commands/addExtension.js +++ b/platform/cli/src/commands/addExtension.js @@ -44,9 +44,7 @@ export default async function addExtension(packageName, version) { .run() .then(ctx => { console.log( - `${chalk.green.bold( - `Added ohif-extension ${packageName}@${ctx.yarnInfo.version}` - )} ` + `${chalk.green.bold(`Added ohif-extension ${packageName}@${ctx.yarnInfo.version}`)} ` ); }) .catch(error => { diff --git a/platform/cli/src/commands/addExtensions.js b/platform/cli/src/commands/addExtensions.js index 5da1d071077..cf5252d13bb 100644 --- a/platform/cli/src/commands/addExtensions.js +++ b/platform/cli/src/commands/addExtensions.js @@ -28,9 +28,7 @@ export default async function addExtensions(ohifExtensions) { extensonsString += ` ${packageName}@${version}`; }); - console.log( - `${chalk.green.bold(`Extensions added:${extensonsString}`)} ` - ); + console.log(`${chalk.green.bold(`Extensions added:${extensonsString}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/addMode.js b/platform/cli/src/commands/addMode.js index 6769261389c..827f2e8650b 100644 --- a/platform/cli/src/commands/addMode.js +++ b/platform/cli/src/commands/addMode.js @@ -39,9 +39,7 @@ export default async function addMode(packageName, version) { { title: 'Detecting required ohif-extensions...', task: async ctx => { - ctx.ohifExtensions = await findRequiredOhifExtensionsForMode( - ctx.yarnInfo - ); + ctx.ohifExtensions = await findRequiredOhifExtensionsForMode(ctx.yarnInfo); }, }, ], @@ -53,11 +51,7 @@ export default async function addMode(packageName, version) { await tasks .run() .then(async ctx => { - console.log( - `${chalk.green.bold( - `Added ohif-mode ${packageName}@${ctx.yarnInfo.version}` - )} ` - ); + console.log(`${chalk.green.bold(`Added ohif-mode ${packageName}@${ctx.yarnInfo.version}`)} `); const ohifExtensions = ctx.ohifExtensions; diff --git a/platform/cli/src/commands/createPackage.js b/platform/cli/src/commands/createPackage.js index 84fa7d45492..3b1f20a5f54 100644 --- a/platform/cli/src/commands/createPackage.js +++ b/platform/cli/src/commands/createPackage.js @@ -10,7 +10,7 @@ import { initGit, } from './utils/index.js'; -const createPackage = async (options) => { +const createPackage = async options => { const { packageType } = options; // extension or mode if (fs.existsSync(options.targetDir)) { @@ -28,11 +28,7 @@ const createPackage = async (options) => { { title: 'Copying template files', task: () => - createDirectoryContents( - options.templateDir, - options.targetDir, - options.prettier - ), + createDirectoryContents(options.templateDir, options.targetDir, options.prettier), }, { title: 'Editing Package.json with provided information', @@ -59,36 +55,20 @@ const createPackage = async (options) => { await tasks.run(); console.log(); - console.log( - chalk.green(`Done: ${packageType} is ready at`, options.targetDir) - ); + console.log(chalk.green(`Done: ${packageType} is ready at`, options.targetDir)); console.log(); - console.log( - chalk.green(`NOTE: In order to use this ${packageType} for development,`) - ); - console.log( - chalk.green( - `run the following command inside the root of the OHIF monorepo` - ) - ); + console.log(chalk.green(`NOTE: In order to use this ${packageType} for development,`)); + console.log(chalk.green(`run the following command inside the root of the OHIF monorepo`)); console.log(); - console.log( - chalk.green.bold( - ` yarn run cli link-${packageType} ${options.targetDir}` - ) - ); + console.log(chalk.green.bold(` yarn run cli link-${packageType} ${options.targetDir}`)); console.log(); console.log( - chalk.yellow( - "and when you don't need it anymore, run the following command to unlink it" - ) + chalk.yellow("and when you don't need it anymore, run the following command to unlink it") ); console.log(); - console.log( - chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`) - ); + console.log(chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`)); console.log(); return true; diff --git a/platform/cli/src/commands/linkPackage.js b/platform/cli/src/commands/linkPackage.js index 23764db23dc..25aaec87f3c 100644 --- a/platform/cli/src/commands/linkPackage.js +++ b/platform/cli/src/commands/linkPackage.js @@ -2,11 +2,7 @@ import fs from 'fs'; import path from 'path'; import { execa } from 'execa'; import { keywords } from './enums/index.js'; -import { - validateYarn, - addExtensionToConfig, - addModeToConfig, -} from './utils/index.js'; +import { validateYarn, addExtensionToConfig, addModeToConfig } from './utils/index.js'; async function linkPackage(packageDir, options, addToConfig, keyword) { const { viewerDirectory } = options; @@ -43,11 +39,7 @@ async function linkPackage(packageDir, options, addToConfig, keyword) { // Add the node_modules of the linked package so that webpack // can find the linked package externals if there are - const webpackPwaPath = path.join( - viewerDirectory, - '.webpack', - 'webpack.pwa.js' - ); + const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js'); async function updateWebpackConfig(webpackConfigPath, packageDir) { const packageNodeModules = path.join(packageDir, 'node_modules'); @@ -56,7 +48,7 @@ async function linkPackage(packageDir, options, addToConfig, keyword) { const newLine = `path.resolve(__dirname, '${packageNodeModules}'),`; const modifiedFileContent = fileContent.replace( /(modules:\s*\[)([\s\S]*?)(\])/, - `$1$2 ${newLine}$3` + `$1$2 ${newLine.replace(/\\/g, '/')}$3` ); await fs.promises.writeFile(webpackConfigPath, modifiedFileContent); diff --git a/platform/cli/src/commands/removeExtension.js b/platform/cli/src/commands/removeExtension.js index e29ec135e84..e5345b2b59a 100644 --- a/platform/cli/src/commands/removeExtension.js +++ b/platform/cli/src/commands/removeExtension.js @@ -19,8 +19,7 @@ export default async function removeExtension(packageName) { }, { title: `Checking if ${packageName} is in use by an installed mode`, - task: async () => - await throwIfExtensionUsedByInstalledMode(packageName), + task: async () => await throwIfExtensionUsedByInstalledMode(packageName), }, { title: `Uninstalling npm package: ${packageName}`, @@ -39,9 +38,7 @@ export default async function removeExtension(packageName) { await tasks .run() .then(() => { - console.log( - `${chalk.green.bold(`Removed ohif-extension ${packageName}`)} ` - ); + console.log(`${chalk.green.bold(`Removed ohif-extension ${packageName}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/removeExtensions.js b/platform/cli/src/commands/removeExtensions.js index 2b29e1be802..951af300611 100644 --- a/platform/cli/src/commands/removeExtensions.js +++ b/platform/cli/src/commands/removeExtensions.js @@ -28,9 +28,7 @@ export default async function removeExtensions(ohifExtensionsToRemove) { extensonsString += ` ${packageName}`; }); - console.log( - `${chalk.green.bold(`Extensions removed:${extensonsString}`)} ` - ); + console.log(`${chalk.green.bold(`Extensions removed:${extensonsString}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/unlinkPackage.js b/platform/cli/src/commands/unlinkPackage.js index 463e7496464..8aaa9f871c7 100644 --- a/platform/cli/src/commands/unlinkPackage.js +++ b/platform/cli/src/commands/unlinkPackage.js @@ -1,11 +1,7 @@ import { execa } from 'execa'; import fs from 'fs'; import path from 'path'; -import { - validateYarn, - removeExtensionFromConfig, - removeModeFromConfig, -} from './utils/index.js'; +import { validateYarn, removeExtensionFromConfig, removeModeFromConfig } from './utils/index.js'; const linkPackage = async (packageName, options, removeFromConfig) => { const { viewerDirectory } = options; @@ -19,11 +15,7 @@ const linkPackage = async (packageName, options, removeFromConfig) => { const results = await execa(`yarn`, ['unlink', packageName]); console.log(results.stdout); - const webpackPwaPath = path.join( - viewerDirectory, - '.webpack', - 'webpack.pwa.js' - ); + const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js'); await removePathFromWebpackConfig(webpackPwaPath, packageName); @@ -58,8 +50,7 @@ async function removePathFromWebpackConfig(webpackConfigPath, packageName) { endIndex++; } - const modifiedFileContent = - fileContent.slice(0, startIndex) + fileContent.slice(endIndex); + const modifiedFileContent = fileContent.slice(0, startIndex) + fileContent.slice(endIndex); await fs.promises.writeFile(webpackConfigPath, modifiedFileContent); } diff --git a/platform/cli/src/commands/utils/createDirectoryContents.js b/platform/cli/src/commands/utils/createDirectoryContents.js index 90a9329c64a..4ea3d65e100 100644 --- a/platform/cli/src/commands/utils/createDirectoryContents.js +++ b/platform/cli/src/commands/utils/createDirectoryContents.js @@ -1,11 +1,7 @@ import fs from 'fs'; // https://github.dev/leoroese/template-cli/blob/628dd24db7df399ebb520edd0bc301bc7b5e8b66/index.js#L19 -const createDirectoryContents = ( - templatePath, - targetDirPath, - copyPrettierRules -) => { +const createDirectoryContents = (templatePath, targetDirPath, copyPrettierRules) => { const filesToCreate = fs.readdirSync(templatePath); filesToCreate.forEach(file => { @@ -22,7 +18,9 @@ const createDirectoryContents = ( const contents = fs.readFileSync(origFilePath, 'utf8'); // Rename - if (file === '.npmignore') file = '.gitignore'; + if (file === '.npmignore') { + file = '.gitignore'; + } const writePath = `${targetDirPath}/${file}`; fs.writeFileSync(writePath, contents, 'utf8'); diff --git a/platform/cli/src/commands/utils/editPackageJson.js b/platform/cli/src/commands/utils/editPackageJson.js index 5ffd8d9a360..4caa7507458 100644 --- a/platform/cli/src/commands/utils/editPackageJson.js +++ b/platform/cli/src/commands/utils/editPackageJson.js @@ -4,10 +4,14 @@ import path from 'path'; async function editPackageJson(options) { const { name, version, description, author, license, targetDir } = options; + const ohifVersion = fs.readFileSync('./version.txt', 'utf8'); + // read package.json from targetDir const dependenciesPath = path.join(targetDir, 'dependencies.json'); const rawData = fs.readFileSync(dependenciesPath, 'utf8'); - const packageJson = JSON.parse(rawData); + + const dataWithOHIFVersion = rawData.replace(/\{LATEST_OHIF_VERSION\}/g, ohifVersion); + const packageJson = JSON.parse(dataWithOHIFVersion); // edit package.json const mergedObj = Object.assign( diff --git a/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js b/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js index be69cc6d142..afb295cb69d 100644 --- a/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js +++ b/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js @@ -1,9 +1,7 @@ import { readPluginConfigFile } from './private/index.js'; import getYarnInfo from './getYarnInfo.js'; -export default async function findOhifExtensionsToRemoveAfterRemovingMode( - removedModeYarnInfo -) { +export default async function findOhifExtensionsToRemoveAfterRemovingMode(removedModeYarnInfo) { const pluginConfig = readPluginConfigFile(); if (!pluginConfig) { @@ -13,27 +11,21 @@ export default async function findOhifExtensionsToRemoveAfterRemovingMode( const { modes, extensions } = pluginConfig; - const registeredExtensions = extensions.map( - extension => extension.packageName - ); + const registeredExtensions = extensions.map(extension => extension.packageName); // TODO this is not a function - const ohifExtensionsOfMode = Object.keys( - removedModeYarnInfo.peerDependencies - ).filter(peerDependency => registeredExtensions.includes(peerDependency)); - - const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map( - packageName => { - return { - packageName, - used: false, - }; - } + const ohifExtensionsOfMode = Object.keys(removedModeYarnInfo.peerDependencies).filter( + peerDependency => registeredExtensions.includes(peerDependency) ); + const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map(packageName => { + return { + packageName, + used: false, + }; + }); + // Check if other modes use each extension used by this mode - const otherModes = modes.filter( - mode => mode.packageName !== removedModeYarnInfo.name - ); + const otherModes = modes.filter(mode => mode.packageName !== removedModeYarnInfo.name); for (let i = 0; i < otherModes.length; i++) { const mode = otherModes[i]; diff --git a/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js b/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js index c907a56b0e5..f895abdd05b 100644 --- a/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js +++ b/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js @@ -11,7 +11,7 @@ export default async function findRequiredOhifExtensionsForMode(yarnInfo) { const dependencies = []; const ohifExtensions = []; - Object.keys(peerDependencies).forEach((packageName) => { + Object.keys(peerDependencies).forEach(packageName => { dependencies.push({ packageName, version: peerDependencies[packageName], diff --git a/platform/cli/src/commands/utils/index.js b/platform/cli/src/commands/utils/index.js index b053c73c932..ee32c1efbc0 100644 --- a/platform/cli/src/commands/utils/index.js +++ b/platform/cli/src/commands/utils/index.js @@ -10,10 +10,7 @@ import { import getYarnInfo from './getYarnInfo.js'; import { addExtensionToConfig, addModeToConfig } from './addToConfig.js'; import findRequiredOhifExtensionsForMode from './findRequiredOhifExtensionsForMode.js'; -import { - removeExtensionFromConfig, - removeModeFromConfig, -} from './removeFromConfig.js'; +import { removeExtensionFromConfig, removeModeFromConfig } from './removeFromConfig.js'; import throwIfExtensionUsedByInstalledMode from './throwIfExtensionUsedByInstalledMode.js'; import findOhifExtensionsToRemoveAfterRemovingMode from './findOhifExtensionsToRemoveAfterRemovingMode.js'; import initGit from './initGit.js'; diff --git a/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js b/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js index 37afbda00cc..119314d7292 100644 --- a/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js +++ b/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js @@ -17,9 +17,7 @@ function removeModeFromConfigJson(pluginConfig, { packageName }) { function removeFromList(listName, pluginConfig, { packageName }) { const list = pluginConfig[listName]; - const indexOfExistingEntry = list.findIndex( - entry => entry.packageName === packageName - ); + const indexOfExistingEntry = list.findIndex(entry => entry.packageName === packageName); if (indexOfExistingEntry !== -1) { pluginConfig[listName].splice(indexOfExistingEntry, 1); diff --git a/platform/cli/src/commands/utils/validate.js b/platform/cli/src/commands/utils/validate.js index bd782a73edc..356dd901f2b 100644 --- a/platform/cli/src/commands/utils/validate.js +++ b/platform/cli/src/commands/utils/validate.js @@ -25,9 +25,7 @@ async function validateExtensionYarnInfo(packageName) { function validateYarnInfo(packageName, keyword) { return new Promise(async (resolve, reject) => { function rejectIfNotFound() { - const error = new Error( - `${chalk.red.bold('Error')} extension ${packageName} not installed` - ); + const error = new Error(`${chalk.red.bold('Error')} extension ${packageName} not installed`); reject(error); } @@ -77,14 +75,14 @@ function getVersion(json, version) { const [majorVersion] = version .split('^')[1] .split('.') - .map((v) => parseInt(v)); + .map(v => parseInt(v)); // Find the version that matches the major version, but is the latest minor version versions - .filter((version) => parseInt(version.split('.')[0]) === majorVersion) + .filter(version => parseInt(version.split('.')[0]) === majorVersion) .sort((a, b) => { - const [majorA, minorA, patchA] = a.split('.').map((v) => parseInt(v)); - const [majorB, minorB, patchB] = b.split('.').map((v) => parseInt(v)); + const [majorA, minorA, patchA] = a.split('.').map(v => parseInt(v)); + const [majorB, minorB, patchB] = b.split('.').map(v => parseInt(v)); if (majorA === majorB) { if (minorA === minorB) { @@ -110,19 +108,17 @@ function validate(packageName, version, keyword) { // Gets the registry of the package. Scoped packages may not be using the global default. const registryUrlOfPackage = registryUrl(scope); - let options = {} - if (process.env.NPM_TOKEN){ + let options = {}; + if (process.env.NPM_TOKEN) { options['headers'] = { - 'Authorization': `Bearer ${process.env.NPM_TOKEN}`, - } + Authorization: `Bearer ${process.env.NPM_TOKEN}`, + }; } const response = await fetch(`${registryUrlOfPackage}${packageName}`, options); const json = await response.json(); if (json.error && json.error === NOT_FOUND) { - const error = new Error( - `${chalk.red.bold('Error')} package ${packageName} not found` - ); + const error = new Error(`${chalk.red.bold('Error')} package ${packageName} not found`); reject(error); return; } @@ -139,27 +135,18 @@ function validate(packageName, version, keyword) { resolve(true); } else { const error = new Error( - `${chalk.red.bold( - 'Error' - )} package ${packageName} is not an ${keyword}` + `${chalk.red.bold('Error')} package ${packageName} is not an ${keyword}` ); reject(error); } } else { // Particular version undefined const error = new Error( - `${chalk.red.bold( - 'Error' - )} version ${packageVersion} of package ${packageName} not found` + `${chalk.red.bold('Error')} version ${packageVersion} of package ${packageName} not found` ); reject(error); } }); } -export { - validateMode, - validateExtension, - validateModeYarnInfo, - validateExtensionYarnInfo, -}; +export { validateMode, validateExtension, validateModeYarnInfo, validateExtensionYarnInfo }; diff --git a/platform/cli/src/index.js b/platform/cli/src/index.js index e6b40563d8f..d14445abb74 100755 --- a/platform/cli/src/index.js +++ b/platform/cli/src/index.js @@ -32,15 +32,11 @@ try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJson.name !== 'ohif-monorepo-root') { console.log(packageJson); - console.log( - chalk.red('ohif-cli must run from the root of the OHIF platform') - ); + console.log(chalk.red('ohif-cli must run from the root of the OHIF platform')); process.exit(1); } } catch (error) { - console.log( - chalk.red('ohif-cli must run from the root of the OHIF platform') - ); + console.log(chalk.red('ohif-cli must run from the root of the OHIF platform')); process.exit(1); } @@ -142,15 +138,11 @@ program program .command('link-extension ') - .description( - 'Links a local OHIF Extension to the Viewer to be used for development' - ) + .description('Links a local OHIF Extension to the Viewer to be used for development') .action(packageDir => { if (!fs.existsSync(packageDir)) { console.log( - chalk.red( - 'The Extension directory does not exist, please provide a valid directory' - ) + chalk.red('The Extension directory does not exist, please provide a valid directory') ); process.exit(1); } @@ -171,16 +163,10 @@ program program .command('link-mode ') - .description( - 'Links a local OHIF Mode to the Viewer to be used for development' - ) + .description('Links a local OHIF Mode to the Viewer to be used for development') .action(packageDir => { if (!fs.existsSync(packageDir)) { - console.log( - chalk.red( - 'The Mode directory does not exist, please provide a valid directory' - ) - ); + console.log(chalk.red('The Mode directory does not exist, please provide a valid directory')); process.exit(1); } linkMode(packageDir, { viewerDirectory }); diff --git a/platform/cli/templates/extension/.prettierrc b/platform/cli/templates/extension/.prettierrc index b80ec6b3474..ef83baaef93 100644 --- a/platform/cli/templates/extension/.prettierrc +++ b/platform/cli/templates/extension/.prettierrc @@ -1,8 +1,11 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid", + "endOfLine": "auto" } diff --git a/platform/cli/templates/extension/babel.config.js b/platform/cli/templates/extension/babel.config.js index 92fbbdeaf95..371f77fcf4f 100644 --- a/platform/cli/templates/extension/babel.config.js +++ b/platform/cli/templates/extension/babel.config.js @@ -1,5 +1,26 @@ +// https://babeljs.io/docs/en/options#babelrcroots +const { extendDefaultPlugins } = require('svgo'); + module.exports = { - plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'], + plugins: [ + [ + 'inline-react-svg', + { + svgo: { + plugins: extendDefaultPlugins([ + { + name: 'removeViewBox', + active: false, + }, + ]), + }, + }, + ], + ['@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 }], + ], env: { test: { presets: [ @@ -10,15 +31,16 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-transform-regenerator', '@babel/plugin-transform-runtime', + '@babel/plugin-transform-typescript', ], }, production: { @@ -26,7 +48,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +57,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/extension/dependencies.json b/platform/cli/templates/extension/dependencies.json index fcbb4101b66..98b0d1157c8 100644 --- a/platform/cli/templates/extension/dependencies.json +++ b/platform/cli/templates/extension/dependencies.json @@ -9,7 +9,7 @@ }, "scripts": { "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", - "dev:dicom-pdf": "yarn run dev", + "dev:my-extension": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", "start": "yarn run dev" @@ -40,14 +40,15 @@ "@babel/plugin-transform-arrow-functions": "^7.16.7", "@babel/plugin-transform-regenerator": "^7.16.7", "@babel/plugin-transform-runtime": "^7.17.0", - "babel-plugin-inline-react-svg": "^2.0.1", "@babel/plugin-transform-typescript": "^7.13.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", - - "babel-eslint": "^8.0.3", - "babel-loader": "^8.0.0-beta.4", + "@babel/plugin-proposal-private-property-in-object":"7.21.11", + "babel-eslint": "9.x", + "babel-loader": "^8.2.4", + "babel-plugin-inline-react-svg": "^2.0.2", + "babel-plugin-module-resolver": "^5.0.0", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^10.2.0", "cross-env": "^7.0.3", diff --git a/platform/cli/templates/extension/src/index.tsx b/platform/cli/templates/extension/src/index.tsx index fa659bced10..24eff3fb893 100644 --- a/platform/cli/templates/extension/src/index.tsx +++ b/platform/cli/templates/extension/src/index.tsx @@ -16,44 +16,28 @@ export default { * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that * this extension is providing. */ - preRegistration: ({ - servicesManager, - commandsManager, - configuration = {}, - }) => {}, + preRegistration: ({ servicesManager, commandsManager, configuration = {} }) => {}, /** * 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. */ - getPanelModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getPanelModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ViewportModule should provide a list of viewports that will be available in OHIF * for Modes to consume and use in the viewports. Each viewport is defined by * {name, component} object. Example of a viewport module is the CornerstoneViewport * that is provided by the Cornerstone extension in OHIF. */ - getViewportModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getViewportModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ToolbarModule should provide a list of tool buttons that will be available in OHIF * for Modes to consume and use in the toolbar. Each tool button is defined by * {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and * splitButton toolButton that the default extension is providing. */ - getToolbarModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getToolbarModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * LayoutTemplateMOdule should provide a list of layout templates that will be * available in OHIF for Modes to consume and use to layout the viewer. @@ -62,22 +46,14 @@ export default { * a Header, left and right sidebars, and a viewport section in the middle * of the viewer. */ - getLayoutTemplateModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getLayoutTemplateModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * 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: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getSopClassHandlerModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * HangingProtocolModule should provide a list of hanging protocols that will be * available in OHIF for Modes to use to decide on the structure of the viewports @@ -85,11 +61,7 @@ export default { * { name, protocols}. Examples include the default hanging protocol provided by * the default extension that shows 2x2 viewports. */ - getHangingProtocolModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getHangingProtocolModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * CommandsModule should provide a list of commands that will be available in OHIF * for Modes to consume and use in the viewports. Each command is defined by @@ -97,30 +69,18 @@ export default { * object of functions, definitions is an object of available commands, their * options, and defaultContext is the default context for the command to run against. */ - getCommandsModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getCommandsModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ContextModule should provide a list of context that will be available in OHIF * and will be provided to the Modes. A context is a state that is shared OHIF. * Context is defined by an object of { name, context, provider }. Examples include * the measurementTracking context provided by the measurementTracking extension. */ - getContextModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getContextModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * DataSourceModule should provide a list of data sources to be used in OHIF. * DataSources can be used to map the external data formats to the OHIF's * native format. DataSources are defined by an object of { name, type, createDataSource }. */ - getDataSourcesModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getDataSourcesModule: ({ servicesManager, commandsManager, extensionManager }) => {}, }; diff --git a/platform/cli/templates/mode/.prettierrc b/platform/cli/templates/mode/.prettierrc index b80ec6b3474..ef83baaef93 100644 --- a/platform/cli/templates/mode/.prettierrc +++ b/platform/cli/templates/mode/.prettierrc @@ -1,8 +1,11 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid", + "endOfLine": "auto" } diff --git a/platform/cli/templates/mode/.webpack/webpack.prod.js b/platform/cli/templates/mode/.webpack/webpack.prod.js index 163392a699a..d0829f3c6a2 100644 --- a/platform/cli/templates/mode/.webpack/webpack.prod.js +++ b/platform/cli/templates/mode/.webpack/webpack.prod.js @@ -39,6 +39,12 @@ const config = { amd: '@ohif/ui', root: '@ohif/ui', }, + '@ohif/mode-longitudinal': { + commonjs2: '@ohif/mode-longitudinal', + commonjs: '@ohif/mode-longitudinal', + amd: '@ohif/mode-longitudinal', + root: '@ohif/mode-longitudinal', + } }, ], module: { diff --git a/platform/cli/templates/mode/babel.config.js b/platform/cli/templates/mode/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/platform/cli/templates/mode/babel.config.js +++ b/platform/cli/templates/mode/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/mode/dependencies.json b/platform/cli/templates/mode/dependencies.json index 5bfa5f161bf..4270837d9fc 100644 --- a/platform/cli/templates/mode/dependencies.json +++ b/platform/cli/templates/mode/dependencies.json @@ -17,7 +17,7 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "^3.0.0" + "@ohif/core": "^{LATEST_OHIF_VERSION}" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/platform/cli/templates/mode/src/index.tsx b/platform/cli/templates/mode/src/index.tsx index 2086aa36b81..2d63d106182 100644 --- a/platform/cli/templates/mode/src/index.tsx +++ b/platform/cli/templates/mode/src/index.tsx @@ -41,11 +41,7 @@ function modeFactory({ modeConfiguration }) { * Services and other resources. */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - measurementService, - toolbarService, - toolGroupService, - } = servicesManager.services; + const { measurementService, toolbarService, toolGroupService } = servicesManager.services; measurementService.clearMeasurements(); @@ -57,7 +53,6 @@ function modeFactory({ modeConfiguration }) { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { diff --git a/platform/core/.all-contributorsrc b/platform/core/.all-contributorsrc index f8dc5c140af..9f6138f4502 100644 --- a/platform/core/.all-contributorsrc +++ b/platform/core/.all-contributorsrc @@ -1,7 +1,5 @@ { - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, "contributors": [ @@ -10,83 +8,63 @@ "name": "Erik Ziegler", "avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4", "profile": "https://github.com/swederik", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "evren217", "name": "Evren Ozkan", "avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4", "profile": "https://github.com/evren217", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "galelis", "name": "Gustavo André Lelis", "avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4", "profile": "https://github.com/galelis", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dannyrb", "name": "Danny Brown", "avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4", "profile": "http://dannyrb.com/", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "allcontributors", "name": "allcontributors[bot]", "avatar_url": "https://avatars3.githubusercontent.com/u/46843839?v=4", "profile": "https://github.com/all-contributors/all-contributors-bot", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "ivan-aksamentov", "name": "Ivan Aksamentov", "avatar_url": "https://avatars0.githubusercontent.com/u/9403403?v=4", "profile": "https://github.com/ivan-aksamentov", - "contributions": [ - "code", - "test" - ] + "contributions": ["code", "test"] }, { "login": "igoroctaviano", "name": "Igor Octaviano", "avatar_url": "https://avatars0.githubusercontent.com/u/13886933?v=4", "profile": "http://igoroctaviano.com", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dlwire", "name": "David Wire", "avatar_url": "https://avatars3.githubusercontent.com/u/1167291?v=4", "profile": "https://github.com/dlwire", - "contributions": [ - "code", - "test" - ] + "contributions": ["code", "test"] }, { "login": "pavertomato", "name": "Egor Lezhnin", "avatar_url": "https://avatars0.githubusercontent.com/u/878990?v=4", "profile": "http://egor.lezhn.in", - "contributions": [ - "code" - ] + "contributions": ["code"] } ], "contributorsPerLine": 7, diff --git a/platform/core/.webpack/webpack.dev.js b/platform/core/.webpack/webpack.dev.js index c496972cdf4..1b8e34cfd13 100644 --- a/platform/core/.webpack/webpack.dev.js +++ b/platform/core/.webpack/webpack.dev.js @@ -3,12 +3,10 @@ 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.ts`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/platform/core/.webpack/webpack.prod.js b/platform/core/.webpack/webpack.prod.js index 4b599bec2ad..beff3bba3ca 100644 --- a/platform/core/.webpack/webpack.prod.js +++ b/platform/core/.webpack/webpack.prod.js @@ -36,11 +36,6 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@cornerstonejs/], }); }; diff --git a/platform/core/CHANGELOG.md b/platform/core/CHANGELOG.md index 64bb01a5a61..dcbd3539cb6 100644 --- a/platform/core/CHANGELOG.md +++ b/platform/core/CHANGELOG.md @@ -3,6 +3,480 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + + +### Bug Fixes + +* **typescript error:** Change pubSubServiceInterface file type to typescript ([#3546](https://github.com/OHIF/Viewers/issues/3546)) ([eb22328](https://github.com/OHIF/Viewers/commit/eb22328fc05d06fc4411805e7a30f826659d796a)) + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + + +### Bug Fixes + +* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1)) + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) + + + + + ## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14) diff --git a/platform/core/README.md b/platform/core/README.md index ac3b6c6bd91..ff8f555467d 100644 --- a/platform/core/README.md +++ b/platform/core/README.md @@ -42,7 +42,7 @@ implementation][react-viewer]. ### Install -> This library is pre- v1.0. All realeases until a v1.0 have the possibility of +> This library is pre- v1.0. All releases until a v1.0 have the possibility of > introducing breaking changes. Please depend on an "exact" version in your > projects to prevent issues caused by loose versioning. diff --git a/platform/core/babel.config.js b/platform/core/babel.config.js index fed6f05fecd..325ca2a8ee7 100644 --- a/platform/core/babel.config.js +++ b/platform/core/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/core/package.json b/platform/core/package.json index 36456d27546..39c2dfd7dd7 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/core", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Generic business logic for web-based medical imaging applications", "author": "OHIF Core Team", "license": "MIT", @@ -35,8 +35,8 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.20.3", + "@ohif/ui": "3.7.0-beta.108", "cornerstone-math": "0.1.9", "dicom-parser": "^1.8.21" }, diff --git a/platform/core/src/DICOMWeb/getAttribute.js b/platform/core/src/DICOMWeb/getAttribute.js index ae6b34b056c..b07f3cd8cd2 100644 --- a/platform/core/src/DICOMWeb/getAttribute.js +++ b/platform/core/src/DICOMWeb/getAttribute.js @@ -25,10 +25,18 @@ function convertToInt(input) { function padFour(input) { const l = input.length; - if (l == 0) return '0000'; - if (l == 1) return '000' + input; - if (l == 2) return '00' + input; - if (l == 3) return '0' + input; + if (l === 0) { + return '0000'; + } + if (l === 1) { + return '000' + input; + } + if (l === 2) { + return '00' + input; + } + if (l === 3) { + return '0' + input; + } return input; } diff --git a/platform/core/src/DICOMWeb/getAttribute.test.js b/platform/core/src/DICOMWeb/getAttribute.test.js index 8432c8dcaad..a0dcc6d3de8 100644 --- a/platform/core/src/DICOMWeb/getAttribute.test.js +++ b/platform/core/src/DICOMWeb/getAttribute.test.js @@ -26,9 +26,7 @@ describe('getAttribute', () => { expect(getAttribute(nullElement, defaultValue)).toEqual(defaultValue); expect(getAttribute(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getAttribute(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getAttribute(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return 48 for element with value 0', () => { diff --git a/platform/core/src/DICOMWeb/getAuthorizationHeader.js b/platform/core/src/DICOMWeb/getAuthorizationHeader.js index a154b746274..4d996bb97d6 100644 --- a/platform/core/src/DICOMWeb/getAuthorizationHeader.js +++ b/platform/core/src/DICOMWeb/getAuthorizationHeader.js @@ -1,4 +1,4 @@ -import 'isomorphic-base64' +import 'isomorphic-base64'; import user from '../user'; /** diff --git a/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js b/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js index dc593bd5f65..9bf0b4636b5 100644 --- a/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js +++ b/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js @@ -28,9 +28,7 @@ describe('getAuthorizationHeader', () => { }; const expectedAuthorizationHeader = { - Authorization: `Basic ${btoa( - validServerWithoutPassword.requestOptions.auth - )}`, + Authorization: `Basic ${btoa(validServerWithoutPassword.requestOptions.auth)}`, }; const authentication = getAuthorizationHeader(validServerWithoutPassword); diff --git a/platform/core/src/DICOMWeb/getModalities.test.js b/platform/core/src/DICOMWeb/getModalities.test.js index debae322dfc..1ce13bb06f7 100644 --- a/platform/core/src/DICOMWeb/getModalities.test.js +++ b/platform/core/src/DICOMWeb/getModalities.test.js @@ -23,9 +23,7 @@ describe('getModalities', () => { vr: 'MOCKED_VALUE', }; - expect(getModalities(Modality, ModalitiesInStudy)).toEqual( - ModalitiesInStudy - ); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(ModalitiesInStudy); }); test('should return only the modalitues that exists in ModalitiesInStudy', () => { @@ -67,8 +65,6 @@ describe('getModalities', () => { vr: 'ANOTHER_VR', }; - expect(getModalities(Modality, ModalitiesInStudy)).toEqual( - ModalitiesInStudy - ); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(ModalitiesInStudy); }); }); diff --git a/platform/core/src/DICOMWeb/getName.test.js b/platform/core/src/DICOMWeb/getName.test.js index b42a928c794..2003cb0ecc2 100644 --- a/platform/core/src/DICOMWeb/getName.test.js +++ b/platform/core/src/DICOMWeb/getName.test.js @@ -32,12 +32,7 @@ describe('getName', () => { it('should return A for element when Alphabetic is [A, B, C, D]', () => { const returnValue = 'A'; const element = { - Value: [ - { Alphabetic: 'A' }, - { Alphabetic: 'B' }, - { Alphabetic: 'C' }, - { Alphabetic: 'D' }, - ], + Value: [{ Alphabetic: 'A' }, { Alphabetic: 'B' }, { Alphabetic: 'C' }, { Alphabetic: 'D' }], }; expect(getName(element, null)).toEqual(returnValue); }); diff --git a/platform/core/src/DICOMWeb/getNumber.test.js b/platform/core/src/DICOMWeb/getNumber.test.js index 18f10524e04..ab0cb0814b8 100644 --- a/platform/core/src/DICOMWeb/getNumber.test.js +++ b/platform/core/src/DICOMWeb/getNumber.test.js @@ -26,9 +26,7 @@ describe('getNumber', () => { expect(getNumber(nullElement, defaultValue)).toEqual(defaultValue); expect(getNumber(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getNumber(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getNumber(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return 2.0 for element when element.Value[0] = 2', () => { diff --git a/platform/core/src/DICOMWeb/getString.test.js b/platform/core/src/DICOMWeb/getString.test.js index 34d02955368..727df7f9ef7 100644 --- a/platform/core/src/DICOMWeb/getString.test.js +++ b/platform/core/src/DICOMWeb/getString.test.js @@ -26,9 +26,7 @@ describe('getString', () => { expect(getString(nullElement, defaultValue)).toEqual(defaultValue); expect(getString(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getString(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getString(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return A,B,C,D for element when element.Value[0] = [A, B, C, D]', () => { diff --git a/platform/core/src/DICOMWeb/index.js b/platform/core/src/DICOMWeb/index.js index 24ac21239a7..0775d927492 100644 --- a/platform/core/src/DICOMWeb/index.js +++ b/platform/core/src/DICOMWeb/index.js @@ -14,13 +14,6 @@ const DICOMWeb = { getString, }; -export { - getAttribute, - getAuthorizationHeader, - getModalities, - getName, - getNumber, - getString, -}; +export { getAttribute, getAuthorizationHeader, getModalities, getName, getNumber, getString }; export default DICOMWeb; diff --git a/platform/core/src/DataSources/IWebApiDataSource.js b/platform/core/src/DataSources/IWebApiDataSource.js index a0b1a713f02..9d9a5581ee7 100644 --- a/platform/core/src/DataSources/IWebApiDataSource.js +++ b/platform/core/src/DataSources/IWebApiDataSource.js @@ -22,6 +22,7 @@ function create({ getImageIdsForDisplaySet, getImageIdsForInstance, getConfig, + getStudyInstanceUIDs, }) { const defaultQuery = { studies: { @@ -73,6 +74,7 @@ function create({ getImageIdsForDisplaySet, getImageIdsForInstance, getConfig: getConfig || defaultGetConfig, + getStudyInstanceUIDs: getStudyInstanceUIDs, }; } diff --git a/platform/core/src/__mocks__/dicomweb-client.js b/platform/core/src/__mocks__/dicomweb-client.js index c076a1d721b..b75c269179f 100644 --- a/platform/core/src/__mocks__/dicomweb-client.js +++ b/platform/core/src/__mocks__/dicomweb-client.js @@ -1,9 +1,9 @@ // import { api } from 'dicomweb-client' const api = { - DICOMwebClient: jest.fn().mockImplementation(function() { + DICOMwebClient: jest.fn().mockImplementation(function () { this.retrieveStudyMetadata = jest.fn().mockResolvedValue([]); - this.retrieveSeriesMetadata = jest.fn(function(options) { + this.retrieveSeriesMetadata = jest.fn(function (options) { const { studyInstanceUID, seriesInstanceUID } = options; return Promise.resolve([{ studyInstanceUID, seriesInstanceUID }]); }); diff --git a/platform/core/src/classes/CommandsManager.test.js b/platform/core/src/classes/CommandsManager.test.js index 7b9fee3ea64..399003834f3 100644 --- a/platform/core/src/classes/CommandsManager.test.js +++ b/platform/core/src/classes/CommandsManager.test.js @@ -94,10 +94,7 @@ describe('CommandsManager', () => { describe('getCommand()', () => { it('returns undefined if context does not exist', () => { - const result = commandsManager.getCommand( - 'TestCommand', - 'NonExistentContext' - ); + const result = commandsManager.getCommand('TestCommand', 'NonExistentContext'); expect(result).toBe(undefined); }); @@ -131,9 +128,7 @@ describe('CommandsManager', () => { describe('runCommand()', () => { it('Logs a warning if commandName not found in context', () => { - const result = commandsManager.runCommand( - 'CommandThatDoesNotExistInAnyContext' - ); + const result = commandsManager.runCommand('CommandThatDoesNotExistInAnyContext'); expect(result).toBe(undefined); expect(log.warn.mock.calls[0][0]).toEqual( @@ -149,16 +144,8 @@ describe('CommandsManager', () => { }; commandsManager.createContext(contextName); - commandsManager.registerCommand( - contextName, - 'TestCommand', - commandWithNoCommmandFn - ); - const result = commandsManager.runCommand( - 'TestCommand', - null, - contextName - ); + commandsManager.registerCommand(contextName, 'TestCommand', commandWithNoCommmandFn); + const result = commandsManager.runCommand('TestCommand', null, contextName); expect(result).toBe(undefined); expect(log.warn.mock.calls[0][0]).toEqual( @@ -192,9 +179,7 @@ describe('CommandsManager', () => { commandsManager.runCommand('TestCommand', runCommandOptions, 'VIEWER'); expect(command.commandFn.mock.calls.length).toBe(1); - expect(command.commandFn.mock.calls[0][0].test).toEqual( - runCommandOptions.test - ); + expect(command.commandFn.mock.calls[0][0].test).toEqual(runCommandOptions.test); }); it('Returns the result of commandFn', () => { diff --git a/platform/core/src/classes/CommandsManager.ts b/platform/core/src/classes/CommandsManager.ts index 34a825c4d32..4d4563a005a 100644 --- a/platform/core/src/classes/CommandsManager.ts +++ b/platform/core/src/classes/CommandsManager.ts @@ -175,34 +175,33 @@ export class CommandsManager { toRun: Command | Commands | Command[] | undefined, options?: Record ): unknown { - if (!toRun) return; + if (!toRun) { + return; + } const commands = (Array.isArray(toRun) && toRun) || ((toRun as Command).commandName && [toRun]) || - (Array.isArray((toRun as Commands).commands) && - (toRun as Commands).commands); + (Array.isArray((toRun as Commands).commands) && (toRun as Commands).commands); if (!commands) { console.log("Command isn't runnable", toRun); return; } let result; - (commands as Command[]).forEach( - ({ commandName, commandOptions, context }) => { - if (commandName) { - result = this.runCommand( - commandName, - { - ...commandOptions, - ...options, - }, - context - ); - } else { - console.warn('No command name supplied in', toRun); - } + (commands as Command[]).forEach(({ commandName, commandOptions, context }) => { + if (commandName) { + result = this.runCommand( + commandName, + { + ...commandOptions, + ...options, + }, + context + ); + } else { + console.warn('No command name supplied in', toRun); } - ); + }); return result; } diff --git a/platform/core/src/classes/HotkeysManager.test.js b/platform/core/src/classes/HotkeysManager.test.js index 3cb68be1795..2eb63def22e 100644 --- a/platform/core/src/classes/HotkeysManager.test.js +++ b/platform/core/src/classes/HotkeysManager.test.js @@ -21,11 +21,7 @@ describe('HotkeysManager', () => { }); it('has expected properties', () => { const allProperties = Object.keys(hotkeysManager); - const expectedProperties = [ - 'hotkeyDefinitions', - 'hotkeyDefaults', - 'isEnabled', - ]; + const expectedProperties = ['hotkeyDefinitions', 'hotkeyDefaults', 'isEnabled']; const containsAllExpectedProperties = expectedProperties.every(expected => allProperties.includes(expected) @@ -134,21 +130,16 @@ describe('HotkeysManager', () => { hotkeysManager.registerHotkeys(definition); - const numOfHotkeyDefinitions = Object.keys( - hotkeysManager.hotkeyDefinitions - ).length; + const numOfHotkeyDefinitions = Object.keys(hotkeysManager.hotkeyDefinitions).length; const commandHash = objectHash({ commandName: definition.commandName, commandOptions: definition.commandOptions, }); - const hotkeyDefinitionForRegisteredCommand = - hotkeysManager.hotkeyDefinitions[commandHash]; + const hotkeyDefinitionForRegisteredCommand = hotkeysManager.hotkeyDefinitions[commandHash]; expect(numOfHotkeyDefinitions).toBe(1); - expect(Object.keys(hotkeysManager.hotkeyDefinitions)[0]).toEqual( - commandHash - ); + expect(Object.keys(hotkeysManager.hotkeyDefinitions)[0]).toEqual(commandHash); expect(hotkeyDefinitionForRegisteredCommand).toEqual(definition); }); it('calls hotkeys.bind for the group of keys', () => { @@ -182,9 +173,7 @@ describe('HotkeysManager', () => { hotkeysManager.restoreDefaultBindings(); - expect(hotkeysManager.setHotkeys.mock.calls[0][0]).toEqual( - hotkeysManager.hotkeyDefaults - ); + expect(hotkeysManager.setHotkeys.mock.calls[0][0]).toEqual(hotkeysManager.hotkeyDefaults); }); }); diff --git a/platform/core/src/classes/HotkeysManager.ts b/platform/core/src/classes/HotkeysManager.ts index 50ccc0fe518..886cafc2d05 100644 --- a/platform/core/src/classes/HotkeysManager.ts +++ b/platform/core/src/classes/HotkeysManager.ts @@ -174,14 +174,7 @@ export class HotkeysManager { * @returns {undefined} */ registerHotkeys( - { - commandName, - commandOptions = {}, - context, - keys, - label, - isEditable, - }: Hotkey = {}, + { commandName, commandOptions = {}, context, keys, label, isEditable }: Hotkey = {}, extension ) { if (!commandName) { @@ -189,9 +182,7 @@ export class HotkeysManager { } const commandHash = objectHash({ commandName, commandOptions }); - const options = Object.keys(commandOptions).length - ? JSON.stringify(commandOptions) - : 'no'; + const options = Object.keys(commandOptions).length ? JSON.stringify(commandOptions) : 'no'; const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash]; if (previouslyRegisteredDefinition) { @@ -255,11 +246,7 @@ export class HotkeysManager { hotkeys.bind(combinedKeys, evt => { evt.preventDefault(); evt.stopPropagation(); - this._commandsManager.runCommand( - commandName, - { evt, ...commandOptions }, - context - ); + this._commandsManager.runCommand(commandName, { evt, ...commandOptions }, context); }); } diff --git a/platform/core/src/classes/ImageSet.ts b/platform/core/src/classes/ImageSet.ts index d7ac82bce92..d541a793e23 100644 --- a/platform/core/src/classes/ImageSet.ts +++ b/platform/core/src/classes/ImageSet.ts @@ -107,7 +107,7 @@ class ImageSet { ) ); - const distanceImagePairs = images.map(function(image: Image) { + const distanceImagePairs = images.map(function (image: Image) { const ippVec = new Vector3(..._getImagePositionPatient(image)); const positionVector = refIppVec.clone().sub(ippVec); const distance = positionVector.dot(scanAxisNormal); @@ -118,13 +118,13 @@ class ImageSet { }; }); - distanceImagePairs.sort(function(a, b) { + distanceImagePairs.sort(function (a, b) { return b.distance - a.distance; }); const sortedImages = distanceImagePairs.map(a => a.image); - images.sort(function(a, b) { + images.sort(function (a, b) { return sortedImages.indexOf(a) - sortedImages.indexOf(b); }); } diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.ts similarity index 75% rename from platform/core/src/classes/MetadataProvider.js rename to platform/core/src/classes/MetadataProvider.ts index 75f1ad9fe35..0ecfae3d801 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.ts @@ -58,21 +58,19 @@ class MetadataProvider { return; } - const { - StudyInstanceUID, - SeriesInstanceUID, - SOPInstanceUID, - frameNumber, - } = uids; + const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, frameNumber } = uids; const instance = DicomMetadataStore.getInstance( StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID ); - return ( - (frameNumber && combineFrameInstance(frameNumber, instance)) || instance - ); + + if (!instance) { + return; + } + + return (frameNumber && combineFrameInstance(frameNumber, instance)) || instance; } get(query, imageId, options = { fallback: false }) { @@ -102,11 +100,7 @@ class MetadataProvider { return this.get(INSTANCE, imageId); } - getTagFromInstance( - naturalizedTagOrWADOImageLoaderTag, - instance, - options = { fallback: false } - ) { + getTagFromInstance(naturalizedTagOrWADOImageLoaderTag, instance, options = { fallback: false }) { if (!instance) { return; } @@ -117,14 +111,22 @@ class MetadataProvider { } // Maybe its a legacy dicomImageLoader tag then: - return this._getCornerstoneDICOMImageLoaderTag( - naturalizedTagOrWADOImageLoaderTag, - instance - ); + return this._getCornerstoneDICOMImageLoaderTag(naturalizedTagOrWADOImageLoaderTag, instance); + } + + /** + * Adds a new handler for the given tag. The handler will be provided an + * instance object that it can read values from. + */ + public addHandler(wadoImageLoaderTag: string, handler) { + WADO_IMAGE_LOADER[wadoImageLoaderTag] = handler; } _getCornerstoneDICOMImageLoaderTag(wadoImageLoaderTag, instance) { - let metadata; + let metadata = WADO_IMAGE_LOADER[wadoImageLoaderTag]?.(instance); + if (metadata) { + return metadata; + } switch (wadoImageLoaderTag) { case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE: @@ -162,47 +164,6 @@ class MetadataProvider { patientSex: instance.PatientSex, }; break; - case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE: - const { ImageOrientationPatient } = instance; - - // Fallback for DX images. - // TODO: We should use the rest of the results of this function - // to update the UI somehow - const { PixelSpacing } = getPixelSpacingInformation(instance); - - let rowPixelSpacing; - let columnPixelSpacing; - - let rowCosines; - let columnCosines; - - if (PixelSpacing) { - rowPixelSpacing = PixelSpacing[0]; - columnPixelSpacing = PixelSpacing[1]; - } - - if (ImageOrientationPatient) { - rowCosines = ImageOrientationPatient.slice(0, 3); - columnCosines = ImageOrientationPatient.slice(3, 6); - } - - metadata = { - frameOfReferenceUID: instance.FrameOfReferenceUID, - rows: toNumber(instance.Rows), - columns: toNumber(instance.Columns), - imageOrientationPatient: toNumber(ImageOrientationPatient), - rowCosines: toNumber(rowCosines || [0, 1, 0]), - columnCosines: toNumber(columnCosines || [0, 0, -1]), - imagePositionPatient: toNumber( - instance.ImagePositionPatient || [0, 0, 0] - ), - sliceThickness: toNumber(instance.SliceThickness), - sliceLocation: toNumber(instance.SliceLocation), - pixelSpacing: toNumber(PixelSpacing || 1), - rowPixelSpacing: toNumber(rowPixelSpacing || 1), - columnPixelSpacing: toNumber(columnPixelSpacing || 1), - }; - break; case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE: metadata = { samplesPerPixel: toNumber(instance.SamplesPerPixel), @@ -249,12 +210,8 @@ class MetadataProvider { if (WindowCenter === undefined || WindowWidth === undefined) { return; } - const windowCenter = Array.isArray(WindowCenter) - ? WindowCenter - : [WindowCenter]; - const windowWidth = Array.isArray(WindowWidth) - ? WindowWidth - : [WindowWidth]; + const windowCenter = Array.isArray(WindowCenter) ? WindowCenter : [WindowCenter]; + const windowWidth = Array.isArray(WindowWidth) ? WindowWidth : [WindowWidth]; metadata = { windowCenter: toNumber(windowCenter), @@ -291,16 +248,11 @@ class MetadataProvider { ? RadiopharmaceuticalInformationSequence[0] : RadiopharmaceuticalInformationSequence; - const { - RadiopharmaceuticalStartTime, - RadionuclideTotalDose, - RadionuclideHalfLife, - } = RadiopharmaceuticalInformation; + const { RadiopharmaceuticalStartTime, RadionuclideTotalDose, RadionuclideHalfLife } = + RadiopharmaceuticalInformation; const radiopharmaceuticalInfo = { - radiopharmaceuticalStartTime: dicomParser.parseTM( - RadiopharmaceuticalStartTime - ), + radiopharmaceuticalStartTime: dicomParser.parseTM(RadiopharmaceuticalStartTime), radionuclideTotalDose: RadionuclideTotalDose, radionuclideHalfLife: RadionuclideHalfLife, }; @@ -313,11 +265,7 @@ class MetadataProvider { case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE: const overlays = []; - for ( - let overlayGroup = 0x00; - overlayGroup <= 0x1e; - overlayGroup += 0x02 - ) { + for (let overlayGroup = 0x00; overlayGroup <= 0x1e; overlayGroup += 0x02) { let groupStr = `60${overlayGroup.toString(16)}`; if (groupStr.length === 3) { @@ -342,12 +290,42 @@ class MetadataProvider { const ROIStandardDeviationTag = `${groupStr}1303`; const OverlayOrigin = instance[OverlayOriginTag]; + let rows = 0; + if (instance[OverlayRowsTag] instanceof Array) { + // The DICOM VR for overlay rows is US (unsigned short). + const rowsInt16Array = new Uint16Array(instance[OverlayRowsTag][0]); + rows = rowsInt16Array[0]; + } else { + rows = instance[OverlayRowsTag]; + } + + let columns = 0; + if (instance[OverlayColumnsTag] instanceof Array) { + // The DICOM VR for overlay columns is US (unsigned short). + const columnsInt16Array = new Uint16Array(instance[OverlayColumnsTag][0]); + columns = columnsInt16Array[0]; + } else { + columns = instance[OverlayColumnsTag]; + } + + let x = 0; + let y = 0; + if (OverlayOrigin.length === 1) { + // The DICOM VR for overlay origin is SS (signed short) with a multiplicity of 2. + const originInt16Array = new Int16Array(OverlayOrigin[0]); + x = originInt16Array[0]; + y = originInt16Array[1]; + } else { + x = OverlayOrigin[0]; + y = OverlayOrigin[1]; + } + const overlay = { - rows: instance[OverlayRowsTag], - columns: instance[OverlayColumnsTag], + rows: rows, + columns: columns, type: instance[OverlayType], - x: OverlayOrigin[0], - y: OverlayOrigin[1], + x, + y, pixelData: OverlayData, description: instance[OverlayDescriptionTag], label: instance[OverlayLabelTag], @@ -405,6 +383,14 @@ class MetadataProvider { }; break; + case WADO_IMAGE_LOADER_TAGS.PER_SERIES_MODULE: + metadata = { + correctedImage: instance.CorrectedImage, + units: instance.Units, + decayCorrection: instance.DecayCorrection, + }; + break; + default: return; } @@ -441,7 +427,9 @@ class MetadataProvider { } getUIDsFromImageID(imageId) { - if (!imageId) throw new Error('MetadataProvider::Empty imageId'); + if (!imageId) { + throw new Error('MetadataProvider::Empty imageId'); + } // TODO: adding csiv here is not really correct. Probably need to use // metadataProvider.addImageIdToUIDs(imageId, { // StudyInstanceUID, @@ -485,7 +473,7 @@ class MetadataProvider { imageURI = imageURI.split('&frame=')[0]; const uids = this.imageURIToUIDs.get(imageURI); - let frameNumber = this.getFrameInformationFromURL(imageId) || '1'; + const frameNumber = this.getFrameInformationFromURL(imageId) || '1'; if (uids && frameNumber !== undefined) { return { ...uids, frameNumber }; @@ -498,20 +486,62 @@ const metadataProvider = new MetadataProvider(); export default metadataProvider; +const WADO_IMAGE_LOADER = { + imagePlaneModule: instance => { + const { ImageOrientationPatient } = instance; + + // Fallback for DX images. + // TODO: We should use the rest of the results of this function + // to update the UI somehow + const { PixelSpacing } = getPixelSpacingInformation(instance); + + let rowPixelSpacing; + let columnPixelSpacing; + + let rowCosines; + let columnCosines; + + if (PixelSpacing) { + rowPixelSpacing = PixelSpacing[0]; + columnPixelSpacing = PixelSpacing[1]; + } + + if (ImageOrientationPatient) { + rowCosines = ImageOrientationPatient.slice(0, 3); + columnCosines = ImageOrientationPatient.slice(3, 6); + } + + return { + frameOfReferenceUID: instance.FrameOfReferenceUID, + rows: toNumber(instance.Rows), + columns: toNumber(instance.Columns), + imageOrientationPatient: toNumber(ImageOrientationPatient), + rowCosines: toNumber(rowCosines || [0, 1, 0]), + columnCosines: toNumber(columnCosines || [0, 0, -1]), + imagePositionPatient: toNumber(instance.ImagePositionPatient || [0, 0, 0]), + sliceThickness: toNumber(instance.SliceThickness), + sliceLocation: toNumber(instance.SliceLocation), + pixelSpacing: toNumber(PixelSpacing || 1), + rowPixelSpacing: rowPixelSpacing ? toNumber(rowPixelSpacing) : null, + columnPixelSpacing: columnPixelSpacing ? toNumber(columnPixelSpacing) : null, + }; + }, +}; + const WADO_IMAGE_LOADER_TAGS = { // dicomImageLoader specific GENERAL_SERIES_MODULE: 'generalSeriesModule', PATIENT_STUDY_MODULE: 'patientStudyModule', - IMAGE_PLANE_MODULE: 'imagePlaneModule', IMAGE_PIXEL_MODULE: 'imagePixelModule', VOI_LUT_MODULE: 'voiLutModule', MODALITY_LUT_MODULE: 'modalityLutModule', SOP_COMMON_MODULE: 'sopCommonModule', PET_ISOTOPE_MODULE: 'petIsotopeModule', + PER_SERIES_MODULE: 'petSeriesModule', OVERLAY_PLANE_MODULE: 'overlayPlaneModule', PATIENT_DEMOGRAPHIC_MODULE: 'patientDemographicModule', - // react-cornerstone-viewport specifc + // react-cornerstone-viewport specific PATIENT_MODULE: 'patientModule', GENERAL_IMAGE_MODULE: 'generalImageModule', GENERAL_STUDY_MODULE: 'generalStudyModule', diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js index 4c12e217917..b4c6098936c 100644 --- a/platform/core/src/defaults/hotkeyBindings.js +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -173,30 +173,31 @@ const bindings = [ label: 'W/L Preset 5', keys: ['5'], }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[6], - label: 'W/L Preset 6', - keys: ['6'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[7], - label: 'W/L Preset 7', - keys: ['7'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[8], - label: 'W/L Preset 8', - keys: ['8'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[9], - label: 'W/L Preset 9', - keys: ['9'], - }, + // These don't exist, so don't try applying them.... + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[6], + // label: 'W/L Preset 6', + // keys: ['6'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[7], + // label: 'W/L Preset 7', + // keys: ['7'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[8], + // label: 'W/L Preset 8', + // keys: ['8'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[9], + // label: 'W/L Preset 9', + // keys: ['9'], + // }, ]; export default bindings; diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index 2af3ce52a0d..5d5fed85be5 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -59,17 +59,11 @@ describe('ExtensionManager.ts', () => { extensionManager.registerExtension = jest.fn(); // SUT - const fakeExtensions = [ - { one: '1' }, - [{ two: '2' }, fakeConfiguration], - { three: '3 ' }, - ]; + const fakeExtensions = [{ one: '1' }, [{ two: '2' }, fakeConfiguration], { three: '3 ' }]; await extensionManager.registerExtensions(fakeExtensions); // Assert - expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual( - fakeConfiguration - ); + expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual(fakeConfiguration); }); }); @@ -104,15 +98,11 @@ describe('ExtensionManager.ts', () => { const undefinedExtension = undefined; const nullExtension = null; - await expect( - extensionManager.registerExtension(undefinedExtension) - ).rejects.toThrow( + await expect(extensionManager.registerExtension(undefinedExtension)).rejects.toThrow( new Error('Attempting to register a null/undefined extension.') ); - await expect( - extensionManager.registerExtension(nullExtension) - ).rejects.toThrow( + await expect(extensionManager.registerExtension(nullExtension)).rejects.toThrow( new Error('Attempting to register a null/undefined extension.') ); }); @@ -120,9 +110,9 @@ describe('ExtensionManager.ts', () => { it('logs a warning if the extension does not have an id', async () => { const extensionWithoutId = {}; - await expect( - extensionManager.registerExtension(extensionWithoutId) - ).rejects.toThrow(new Error('Extension ID not set')); + await expect(extensionManager.registerExtension(extensionWithoutId)).rejects.toThrow( + new Error('Extension ID not set') + ); }); it('tracks which extensions have been registered', () => { @@ -156,9 +146,7 @@ describe('ExtensionManager.ts', () => { extensionManager.registerExtension(extensionWithBadModule); expect(log.warn.mock.calls.length).toBe(1); - expect(log.warn.mock.calls[0][0]).toContain( - 'Null or undefined returned when registering' - ); + expect(log.warn.mock.calls[0][0]).toContain('Null or undefined returned when registering'); }); it('logs an error if an exception is thrown while retrieving a module', async () => { @@ -169,9 +157,7 @@ describe('ExtensionManager.ts', () => { }, }; - await expect( - extensionManager.registerExtension(extensionWithBadModule) - ).rejects.toThrow(); + await expect(extensionManager.registerExtension(extensionWithBadModule)).rejects.toThrow(); }); it('successfully passes dependencies to each module along with extension configuration', () => { diff --git a/platform/core/src/extensions/ExtensionManager.ts b/platform/core/src/extensions/ExtensionManager.ts index c028fbab35a..aaf6df8918a 100644 --- a/platform/core/src/extensions/ExtensionManager.ts +++ b/platform/core/src/extensions/ExtensionManager.ts @@ -1,8 +1,9 @@ import MODULE_TYPES from './MODULE_TYPES'; import log from '../log'; import { AppConfig } from '../types/AppConfig'; -import { ServicesManager } from '../services'; +import { PubSubService, ServicesManager } from '../services'; import { HotkeysManager, CommandsManager } from '../classes'; +import { DataSourceDefinition } from '../types'; /** * This is the arguments given to create the extension. @@ -26,6 +27,7 @@ export type ExtensionConfiguration = Record; */ export interface ExtensionParams extends ExtensionConstructor { extensionManager: ExtensionManager; + servicesManager: ServicesManager; configuration?: ExtensionConfiguration; } @@ -42,6 +44,7 @@ export interface Extension { getViewportModule?: (p: ExtensionParams) => unknown; getUtilityModule?: (p: ExtensionParams) => unknown; getCustomizationModule?: (p: ExtensionParams) => unknown; + getSopClassHandlerModule?: (p: ExtensionParams) => unknown; onModeEnter?: () => void; onModeExit?: () => void; } @@ -57,7 +60,11 @@ export type CommandsModule = { defaultContext?: string; }; -export default class ExtensionManager { +export default class ExtensionManager extends PubSubService { + public static readonly EVENTS = { + ACTIVE_DATA_SOURCE_CHANGED: 'event::activedatasourcechanged', + }; + private _commandsManager: CommandsManager; private _servicesManager: ServicesManager; private _hotkeysManager: HotkeysManager; @@ -68,6 +75,7 @@ export default class ExtensionManager { hotkeysManager, appConfig = {}, }: ExtensionConstructor) { + super(ExtensionManager.EVENTS); this.modules = {}; this.registeredExtensionIds = []; this.moduleTypeNames = Object.values(MODULE_TYPES); @@ -85,11 +93,20 @@ export default class ExtensionManager { this.dataSourceMap = {}; this.dataSourceDefs = {}; this.defaultDataSourceName = appConfig.defaultDataSourceName; - this.activeDataSource = undefined; + this.activeDataSource = appConfig.defaultDataSourceName; } - public setActiveDataSource(dataSourceName: string): void { - this.activeDataSource = dataSourceName; + public setActiveDataSource(dataSource: string): void { + if (this.activeDataSource === dataSource) { + return; + } + + this.activeDataSource = dataSource; + + this._broadcastEvent( + ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, + this.dataSourceDefs[this.activeDataSource] + ); } /** @@ -128,12 +145,8 @@ export default class ExtensionManager { } public onModeExit(): void { - const { - registeredExtensionIds, - _servicesManager, - _commandsManager, - _extensionLifeCycleHooks, - } = this; + const { registeredExtensionIds, _servicesManager, _commandsManager, _extensionLifeCycleHooks } = + this; registeredExtensionIds.forEach(extensionId => { const onModeExit = _extensionLifeCycleHooks.onModeExit[extensionId]; @@ -164,10 +177,7 @@ export default class ExtensionManager { * @param {Object[]} extensions - Array of extensions */ public registerExtensions = async ( - extensions: ( - | ExtensionRegister - | [ExtensionRegister, ExtensionConfiguration] - )[], + extensions: (ExtensionRegister | [ExtensionRegister, ExtensionConfiguration])[], dataSources: unknown[] = [] ): Promise => { // Todo: we ideally should be able to run registrations in parallel @@ -186,11 +196,7 @@ export default class ExtensionManager { // for (const extension of extensions) const ohifExtension = extension[0]; const configuration = extension[1]; - await this.registerExtension( - ohifExtension, - configuration, - dataSources - ); + await this.registerExtension(ohifExtension, configuration, dataSources); } else { await this.registerExtension(extension, {}, dataSources); } @@ -243,13 +249,11 @@ export default class ExtensionManager { } if (extension.onModeEnter) { - this._extensionLifeCycleHooks.onModeEnter[extensionId] = - extension.onModeEnter; + this._extensionLifeCycleHooks.onModeEnter[extensionId] = extension.onModeEnter; } if (extension.onModeExit) { - this._extensionLifeCycleHooks.onModeExit[extensionId] = - extension.onModeExit; + this._extensionLifeCycleHooks.onModeExit[extensionId] = extension.onModeExit; } // Register Modules @@ -267,11 +271,7 @@ export default class ExtensionManager { this._initCommandsModule(extensionModule); break; case MODULE_TYPES.DATA_SOURCE: - this._initDataSourcesModule( - extensionModule, - extensionId, - dataSources - ); + this._initDataSourcesModule(extensionModule, extensionId, dataSources); break; case MODULE_TYPES.HANGING_PROTOCOL: this._initHangingProtocolsModule(extensionModule, extensionId); @@ -330,8 +330,27 @@ export default class ExtensionManager { return this.dataSourceMap[this.activeDataSource]; }; - getDataSource = () => { - return this.dataSourceMap[this.activeDataSource]; + /** + * Gets the data source definition for the given data source name. + * If no data source name is provided, the active data source definition is + * returned. + * @param dataSourceName the data source name + * @returns the data source definition + */ + getDataSourceDefinition = dataSourceName => { + if (dataSourceName === undefined) { + // Default to the activeDataSource + dataSourceName = this.activeDataSource; + } + + return this.dataSourceDefs[dataSourceName]; + }; + + /** + * Gets the data source definition for the active data source. + */ + getActiveDataSourceDefinition = () => { + return this.getDataSourceDefinition(this.activeDataSource); }; /** @@ -383,10 +402,81 @@ export default class ExtensionManager { }); }; - _initDataSourcesModule(extensionModule, extensionId, dataSources = []) { + /** + * Adds the given data source and optionally sets it as the active data source. + * The method does this by first creating the data source. + * @param dataSourceDef the data source definition to be added + * @param activate flag to indicate if the added data source should be set to the active data source + */ + addDataSource(dataSourceDef: DataSourceDefinition, options = { activate: false }) { + const existingDataSource = this.getDataSources(dataSourceDef.sourceName); + if (existingDataSource?.[0]) { + // The data source already exists and cannot be added. + return; + } + + this._createDataSourceInstance(dataSourceDef); + + if (options.activate) { + this.setActiveDataSource(dataSourceDef.sourceName); + } + } + + /** + * Updates the configuration of the given data source name. It first creates a new data source with + * the existing definition and the new configuration passed in. + * @param dataSourceName the name of the data source to update + * @param dataSourceConfiguration the new configuration to update the data source with + */ + updateDataSourceConfiguration(dataSourceName: string, dataSourceConfiguration: any) { + const existingDataSource = this.getDataSources(dataSourceName); + if (!existingDataSource?.[0]) { + // Cannot update a non existent data source. + return; + } + + const dataSourceDef = this.dataSourceDefs[dataSourceName]; + // Update the configuration. + dataSourceDef.configuration = dataSourceConfiguration; + this._createDataSourceInstance(dataSourceDef); + + if (this.activeDataSource === dataSourceName) { + // When the active data source is changed/set, fire an event to indicate that its configuration has changed. + this._broadcastEvent(ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, dataSourceDef); + } + } + + /** + * Creates a data source instance from the given definition. The definition is + * added to dataSourceDefs and the created instance is added to dataSourceMap. + * @param dataSourceDef + * @returns + */ + _createDataSourceInstance(dataSourceDef: DataSourceDefinition) { + const module = this.getModuleEntry(dataSourceDef.namespace); + + if (!module) { + return; + } + + this.dataSourceDefs[dataSourceDef.sourceName] = dataSourceDef; + const { userAuthenticationService } = this._servicesManager.services; - dataSources.forEach(dataSource => { - this.dataSourceDefs[dataSource.sourceName] = dataSource; + const dataSourceInstance = module.createDataSource( + dataSourceDef.configuration, + userAuthenticationService + ); + + this.dataSourceMap[dataSourceDef.sourceName] = [dataSourceInstance]; + } + + _initDataSourcesModule( + extensionModule, + extensionId, + dataSources: Array = [] + ): void { + extensionModule.forEach(element => { + this.modulesMap[`${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`] = element; }); extensionModule.forEach(element => { @@ -394,25 +484,10 @@ export default class ExtensionManager { dataSources.forEach(dataSource => { if (dataSource.namespace === namespace) { - const dataSourceInstance = element.createDataSource( - dataSource.configuration, - userAuthenticationService - ); - - if (this.dataSourceMap[dataSource.sourceName]) { - this.dataSourceMap[dataSource.sourceName].push(dataSourceInstance); - } else { - this.dataSourceMap[dataSource.sourceName] = [dataSourceInstance]; - } + this.addDataSource(dataSource); } }); }); - - extensionModule.forEach(element => { - this.modulesMap[ - `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}` - ] = element; - }); } /** @@ -436,8 +511,7 @@ export default class ExtensionManager { Object.keys(definitions).forEach(commandName => { const commandDefinition = definitions[commandName]; const commandHasContextThatDoesNotExist = - commandDefinition.context && - !this._commandsManager.getContext(commandDefinition.context); + commandDefinition.context && !this._commandsManager.getContext(commandDefinition.context); if (commandHasContextThatDoesNotExist) { this._commandsManager.createContext(commandDefinition.context); diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index 3a62a5141de..85ee6817645 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -39,6 +39,8 @@ describe('Top level exports', () => { 'UserAuthenticationService', 'IWebApiDataSource', 'DicomMetadataStore', + 'DisplaySetMessage', + 'DisplaySetMessageList', 'pubSubServiceInterface', 'PubSubService', 'PanelService', diff --git a/platform/core/src/index.ts b/platform/core/src/index.ts index 458c7a3ab8c..67836da4d3c 100644 --- a/platform/core/src/index.ts +++ b/platform/core/src/index.ts @@ -33,6 +33,8 @@ import { PanelService, } from './services'; +import { DisplaySetMessage, DisplaySetMessageList } from './services/DisplaySetService'; + import IWebApiDataSource from './DataSources/IWebApiDataSource'; const hotkeys = { @@ -107,6 +109,8 @@ export { UINotificationService, UIViewportDialogService, DisplaySetService, + DisplaySetMessage, + DisplaySetMessageList, MeasurementService, ToolbarService, ViewportGridService, diff --git a/platform/core/src/log.js b/platform/core/src/log.js index ada577d1a32..c44d1646414 100644 --- a/platform/core/src/log.js +++ b/platform/core/src/log.js @@ -4,8 +4,25 @@ const log = { info: console.log, trace: console.trace, debug: console.debug, - time: console.time, - timeEnd: console.timeEnd, + time: key => { + log.timingKeys[key] = true; + console.time(key); + }, + timeEnd: key => { + if (!log.timingKeys[key]) { + return; + } + log.timingKeys[key] = false; + console.timeEnd(key); + }, + // Store the timing keys to allow knowing whether or not to log events + timingKeys: { + // script time values are added during the index.html initial load, + // before log (this file) is loaded, and the log + // can't depend on the enums, so for this case recreate the string. + // See TimingEnum for details + scriptToView: true, + }, }; export default log; diff --git a/platform/core/src/object.js b/platform/core/src/object.js index 030793f52b5..6201b212ca4 100644 --- a/platform/core/src/object.js +++ b/platform/core/src/object.js @@ -2,7 +2,9 @@ function getNestedObject(shallowObject) { const nestedObject = {}; for (let key in shallowObject) { - if (!shallowObject.hasOwnProperty(key)) continue; + if (!shallowObject.hasOwnProperty(key)) { + continue; + } const value = shallowObject[key]; const propertyArray = key.split('.'); let currentObject = nestedObject; @@ -28,7 +30,9 @@ function getShallowObject(nestedObject) { const shallowObject = {}; const putValues = (baseKey, nestedObject, resultObject) => { for (let key in nestedObject) { - if (!nestedObject.hasOwnProperty(key)) continue; + if (!nestedObject.hasOwnProperty(key)) { + continue; + } let currentKey = baseKey ? `${baseKey}.${key}` : key; const currentValue = nestedObject[key]; if (typeof currentValue === 'object') { diff --git a/platform/core/src/services/CustomizationService/CustomizationService.test.js b/platform/core/src/services/CustomizationService/CustomizationService.test.js index 959816ea576..763bd62e318 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.test.js +++ b/platform/core/src/services/CustomizationService/CustomizationService.test.js @@ -59,29 +59,27 @@ describe('CustomizationService.ts', () => { it('configurationRegistered', () => { configuration.testItem = testItem; customizationService.init(extensionManager); - expect(customizationService.getGlobalCustomization('testItem')).toBe( - testItem - ); + expect(customizationService.getGlobalCustomization('testItem')).toBe(testItem); }); it('defaultRegistered', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [testItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [testItem], + }; customizationService.init(extensionManager); - expect(customizationService.getGlobalCustomization('testItem')).toBe( - testItem - ); + expect(customizationService.getGlobalCustomization('testItem')).toBe(testItem); }); }); describe('customizationType', () => { it('inherits type', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); @@ -96,9 +94,10 @@ describe('CustomizationService.ts', () => { it('inline default inherits type', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); @@ -121,20 +120,17 @@ describe('CustomizationService.ts', () => { describe('mode customization', () => { it('onModeEnter can add extensions', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; customizationService.init(extensionManager); - expect( - customizationService.getModeCustomization('testItem') - ).toBeUndefined(); + expect(customizationService.getModeCustomization('testItem')).toBeUndefined(); customizationService.addModeCustomizations([testItem]); - expect( - customizationService.getGlobalCustomization('testItem') - ).toBeUndefined(); + expect(customizationService.getGlobalCustomization('testItem')).toBeUndefined(); const item = customizationService.getModeCustomization('testItem'); @@ -147,16 +143,15 @@ describe('CustomizationService.ts', () => { it('global customizations override modes', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); // Add a mode customization that would otherwise fail below - customizationService.addModeCustomizations([ - { ...testItem, label: 'other' }, - ]); + customizationService.addModeCustomizations([{ ...testItem, label: 'other' }]); const item = customizationService.getModeCustomization('testItem'); diff --git a/platform/core/src/services/CustomizationService/CustomizationService.ts b/platform/core/src/services/CustomizationService/CustomizationService.ts index cf3ee6a5f27..be8639d5e02 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.ts +++ b/platform/core/src/services/CustomizationService/CustomizationService.ts @@ -12,8 +12,12 @@ const flattenNestedStrings = ( strs: NestedStrings | string, ret?: Record ): Record => { - if (!ret) ret = {}; - if (!strs) return ret; + if (!ret) { + ret = {}; + } + if (!strs) { + return ret; + } if (Array.isArray(strs)) { for (const val of strs) { flattenNestedStrings(val, ret); @@ -81,7 +85,9 @@ export default class CustomizationService extends PubSubService { this.extensionManager.registeredExtensionIds.forEach(extensionId => { const key = `${extensionId}.customizationModule.default`; const defaultCustomizations = this.findExtensionValue(key); - if (!defaultCustomizations) return; + if (!defaultCustomizations) { + return; + } const { value } = defaultCustomizations; this.addReference(value, true); }); @@ -101,10 +107,7 @@ export default class CustomizationService extends PubSubService { return this.modeCustomizations; } - public setModeCustomization( - customizationId: string, - customization: Customization - ): void { + public setModeCustomization(customizationId: string, customization: Customization): void { this.modeCustomizations[customizationId] = merge( this.modeCustomizations[customizationId] || {}, customization @@ -149,10 +152,7 @@ export default class CustomizationService extends PubSubService { } public hasModeCustomization(customizationId: string) { - return ( - this.globalCustomizations[customizationId] || - this.modeCustomizations[customizationId] - ); + return this.globalCustomizations[customizationId] || this.modeCustomizations[customizationId]; } /** * get is an alias for getModeCustomization, as it is the generic getter @@ -172,13 +172,15 @@ export default class CustomizationService extends PubSubService { * type into the new type, allowing default behaviour to be configured. */ public transform(customization: Customization): Customization { - if (!customization) return customization; + if (!customization) { + return customization; + } const { customizationType } = customization; - if (!customizationType) return customization; + if (!customizationType) { + return customization; + } const parent = this.getCustomization(customizationType); - const result = parent - ? Object.assign(Object.create(parent), customization) - : customization; + const result = parent ? Object.assign(Object.create(parent), customization) : customization; // Execute an nested type information return result.transform?.(this) || result; } @@ -203,10 +205,7 @@ export default class CustomizationService extends PubSubService { * the modes. They include things like settings for the search screen. * Reset does NOT clear global customizations. */ - getGlobalCustomization( - id: string, - defaultValue?: Customization - ): Customization | void { + getGlobalCustomization(id: string, defaultValue?: Customization): Customization | void { return this.transform(this.globalCustomizations[id] ?? defaultValue); } @@ -215,15 +214,10 @@ export default class CustomizationService extends PubSubService { this._broadcastGlobalCustomizationModified(); } - protected setConfigGlobalCustomization( - configuration: AppConfigCustomization - ): void { + protected setConfigGlobalCustomization(configuration: AppConfigCustomization): void { this.globalCustomizations = {}; const keys = flattenNestedStrings(configuration.globalCustomizations); - this.readCustomizationTypes( - v => keys[v.name] && v.customization, - this.globalCustomizations - ); + this.readCustomizationTypes(v => keys[v.name] && v.customization, this.globalCustomizations); // TODO - iterate over customizations, loading them from the extension // manager. @@ -242,7 +236,9 @@ export default class CustomizationService extends PubSubService { * or a customization itself. */ addReference(value?: Obj | string, isGlobal = true, id?: string): void { - if (!value) return; + if (!value) { + return; + } if (typeof value === 'string') { const extensionValue = this.findExtensionValue(value); // The child of a reference is only a set of references when an array, @@ -252,10 +248,7 @@ export default class CustomizationService extends PubSubService { this.addReferences(value, isGlobal); } else { const useId = value.id || id; - this[isGlobal ? 'setGlobalCustomization' : 'setModeCustomization']( - useId as string, - value - ); + this[isGlobal ? 'setGlobalCustomization' : 'setModeCustomization'](useId as string, value); } } @@ -265,7 +258,9 @@ export default class CustomizationService extends PubSubService { * or customization. */ addReferences(references?: Obj | Obj[], isGlobal = true): void { - if (!references) return; + if (!references) { + return; + } if (Array.isArray(references)) { references.forEach(item => { this.addReference(item, isGlobal); diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts index f90dd2039ae..841e72e26c1 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts @@ -54,9 +54,7 @@ function _getStudyInstanceUIDs() { } function _getStudy(StudyInstanceUID) { - return _model.studies.find( - aStudy => aStudy.StudyInstanceUID === StudyInstanceUID - ); + return _model.studies.find(aStudy => aStudy.StudyInstanceUID === StudyInstanceUID); } function _getSeries(StudyInstanceUID, SeriesInstanceUID) { @@ -66,9 +64,7 @@ function _getSeries(StudyInstanceUID, SeriesInstanceUID) { return; } - return study.series.find( - aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID - ); + return study.series.find(aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID); } function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { @@ -78,9 +74,7 @@ function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { return; } - return series.instances.find( - instance => instance.SOPInstanceUID === SOPInstanceUID - ); + return series.getInstance(SOPInstanceUID); } function _getInstanceByImageId(imageId) { @@ -102,20 +96,14 @@ function _getInstanceByImageId(imageId) { * @param {*} metadata metadata inform of key value pairs * @returns */ -function _updateMetadataForSeries( - StudyInstanceUID, - SeriesInstanceUID, - metadata -) { +function _updateMetadataForSeries(StudyInstanceUID, SeriesInstanceUID, metadata) { const study = _getStudy(StudyInstanceUID); if (!study) { return; } - const series = study.series.find( - aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID - ); + const series = study.series.find(aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID); const { instances } = series; // update all instances metadata for this series with the new metadata @@ -149,9 +137,7 @@ const BaseImplementation = { // If Arraybuffer, parse to DICOMJSON before naturalizing. if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) { - const dicomData = dcmjs.data.DicomMessage.readFile( - dicomJSONDatasetOrP10ArrayBuffer - ); + const dicomData = dcmjs.data.DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer); dicomJSONDataset = dicomData.dict; } else { @@ -161,18 +147,14 @@ const BaseImplementation = { let naturalizedDataset; if (dicomJSONDataset['SeriesInstanceUID'] === undefined) { - naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( - dicomJSONDataset - ); + naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomJSONDataset); } else { naturalizedDataset = dicomJSONDataset; } const { StudyInstanceUID } = naturalizedDataset; - let study = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!study) { _model.studies.push(createStudyMetadata(StudyInstanceUID)); @@ -184,9 +166,7 @@ const BaseImplementation = { addInstances(instances, madeInClient = false) { const { StudyInstanceUID, SeriesInstanceUID } = instances[0]; - let study = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!study) { _model.studies.push(createStudyMetadata(StudyInstanceUID)); @@ -236,9 +216,7 @@ const BaseImplementation = { addStudy(study) { const { StudyInstanceUID } = study; - const existingStudy = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + const existingStudy = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!existingStudy) { const newStudy = createStudyMetadata(StudyInstanceUID); diff --git a/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js index 51bbf0a5430..1dc6330dbcc 100644 --- a/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js @@ -1,9 +1,26 @@ -function createSeriesMetadata(instances) { - const { SeriesInstanceUID } = instances[0]; +function createSeriesMetadata(SeriesInstanceUID) { + const instances = []; + const instancesMap = new Map(); return { SeriesInstanceUID, instances, + addInstance: function (newInstance) { + this.addInstances([newInstance]); + }, + addInstances: function (newInstances) { + for (let i = 0, len = newInstances.length; i < len; i++) { + const instance = newInstances[i]; + + if (!instancesMap.has(instance.SOPInstanceUID)) { + instancesMap.set(instance.SOPInstanceUID, instance); + instances.push(instance); + } + } + }, + getInstance: function (SOPInstanceUID) { + return instancesMap.get(SOPInstanceUID); + }, }; } diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js index dc43aa503fb..eaaab192a5b 100644 --- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js @@ -8,71 +8,39 @@ function createStudyMetadata(StudyInstanceUID) { isLoaded: false, series: [], /** - * * @param {object} instance - * @returns {bool} true if series were added; false if series already exist */ - addInstanceToSeries: function(instance) { - const { SeriesInstanceUID } = instance; - if (!this.StudyDescription) { - this.StudyDescription = instance.StudyDescription; - } - const existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); - - if (existingSeries) { - existingSeries.instances.push(instance); - } else { - const series = createSeriesMetadata([instance]); - this.series.push(series); - const { Modality } = series; - if (this.ModalitiesInStudy.indexOf(Modality) === -1) { - this.ModalitiesInStudy.push(Modality); - } - } + addInstanceToSeries: function (instance) { + this.addInstancesToSeries([instance]); }, /** - * * @param {object[]} instances * @param {string} instances[].SeriesInstanceUID * @param {string} instances[].StudyDescription - * @returns {bool} true if series were added; false if series already exist */ - addInstancesToSeries: function(instances) { + addInstancesToSeries: function (instances) { const { SeriesInstanceUID } = instances[0]; if (!this.StudyDescription) { this.StudyDescription = instances[0].StudyDescription; } - const existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); + let series = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); - if (existingSeries) { - // Only add instances not already present, so generate a map - // of existing instances and filter the to add by things - // already present. - const sopMap = {}; - existingSeries.instances.forEach( - it => (sopMap[it.SOPInstanceUID] = it) - ); - const newInstances = instances.filter(it => !sopMap[it.SOPInstanceUID]); - existingSeries.instances.push(...newInstances); - } else { - const series = createSeriesMetadata(instances); + if (!series) { + series = createSeriesMetadata(SeriesInstanceUID); this.series.push(series); } + + series.addInstances(instances); }, - setSeriesMetadata: function(SeriesInstanceUID, seriesMetadata) { - let existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); + setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) { + let existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); if (existingSeries) { existingSeries = Object.assign(existingSeries, seriesMetadata); } else { - this.series.push(Object.assign({ instances: [] }, seriesMetadata)); + const series = createSeriesMetadata(SeriesInstanceUID); + this.series.push(Object.assign(series, seriesMetadata)); } }, }; diff --git a/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts new file mode 100644 index 00000000000..6a139daf637 --- /dev/null +++ b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts @@ -0,0 +1,50 @@ +/** + * Defines a displaySet message, that could be any pf the potential problems of a displaySet + */ +class DisplaySetMessage { + id: number; + static CODES = { + NO_VALID_INSTANCES: 1, + NO_POSITION_INFORMATION: 2, + NOT_RECONSTRUCTABLE: 3, + MULTIFRAME_NO_PIXEL_MEASUREMENTS: 4, + MULTIFRAME_NO_ORIENTATION: 5, + MULTIFRAME_NO_POSITION_INFORMATION: 6, + MISSING_FRAMES: 7, + IRREGULAR_SPACING: 8, + INCONSISTENT_DIMENSIONS: 9, + INCONSISTENT_COMPONENTS: 10, + INCONSISTENT_ORIENTATIONS: 11, + INCONSISTENT_POSITION_INFORMATION: 12, + UNSUPPORTED_DISPLAYSET: 13, + }; + + constructor(id: number) { + this.id = id; + } +} +/** + * Defines a list of displaySet messages + */ +class DisplaySetMessageList { + messages = []; + + public addMessage(messageId: number): void { + const message = new DisplaySetMessage(messageId); + this.messages.push(message); + } + + public size(): number { + return this.messages.length; + } + + public includesMessage(messageId: number): boolean { + return this.messages.some(message => message.id === messageId); + } + + public includesAllMessages(messageIdList: number[]): boolean { + return messageIdList.every(messageId => this.include(messageId)); + } +} + +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.ts b/platform/core/src/services/DisplaySetService/DisplaySetService.ts index 5796afa9030..8d01a28dca7 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.ts +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.ts @@ -1,15 +1,8 @@ -import { InstanceMetadata } from '../../types'; +import { ExtensionManager } from '../../extensions'; +import { DisplaySet, InstanceMetadata } from '../../types'; import { PubSubService } from '../_shared/pubSubServiceInterface'; import EVENTS from './EVENTS'; -export type DisplaySet = { - displaySetInstanceUID: string; - instances: InstanceMetadata[]; - StudyInstanceUID: string; - SeriesInstanceUID?: string; - numImages?: number; -}; - const displaySetCache = new Map(); /** @@ -26,15 +19,11 @@ const filterInstances = ( if (!dsInstances) { console.warn('No instances in', ds); } else { - dsInstances.forEach(instance => - dsInstancesSOP.add(instance.SOPInstanceUID) - ); + dsInstances.forEach(instance => dsInstancesSOP.add(instance.SOPInstanceUID)); } }); - return instances.filter( - instance => !dsInstancesSOP.has(instance.SOPInstanceUID) - ); + return instances.filter(instance => !dsInstancesSOP.has(instance.SOPInstanceUID)); }; export default class DisplaySetService extends PubSubService { @@ -47,6 +36,8 @@ export default class DisplaySetService extends PubSubService { }; public activeDisplaySets = []; + public unsuportedSOPClassHandler; + extensionManager: ExtensionManager; protected activeDisplaySetsMap = new Map(); @@ -56,6 +47,8 @@ export default class DisplaySetService extends PubSubService { constructor() { super(EVENTS); + this.unsuportedSOPClassHandler = + '@ohif/extension-default.sopClassHandlerModule.not-supported-display-sets-handler'; } public init(extensionManager, SOPClassHandlerIds): void { @@ -83,6 +76,14 @@ export default class DisplaySetService extends PubSubService { }); } + /** + * Sets the handler for unsupported sop classes + * @param sopClassHandlerUID + */ + public setUnsuportedSOPClassHandler(sopClassHandler) { + this.unsuportedSOPClassHandler = sopClassHandler; + } + /** * Adds new display sets directly, as specified. * Use this function when the display sets are created externally directly @@ -114,9 +115,7 @@ export default class DisplaySetService extends PubSubService { return this.activeDisplaySets; } - public getDisplaySetsForSeries = ( - seriesInstanceUID: string - ): DisplaySet[] => { + public getDisplaySetsForSeries = (seriesInstanceUID: string): DisplaySet[] => { return [...displaySetCache.values()].filter( displaySet => displaySet.SeriesInstanceUID === seriesInstanceUID ); @@ -132,15 +131,16 @@ export default class DisplaySetService extends PubSubService { : [...this.getDisplaySetCache().values()]; const displaySet = displaySets.find(ds => { - return ( - ds.images && ds.images.some(i => i.SOPInstanceUID === sopInstanceUID) - ); + return ds.images && ds.images.some(i => i.SOPInstanceUID === sopInstanceUID); }); return displaySet; } - public setDisplaySetMetadataInvalidated(displaySetInstanceUID: string): void { + public setDisplaySetMetadataInvalidated( + displaySetInstanceUID: string, + invalidateData = true + ): void { const displaySet = this.getDisplaySetByUID(displaySetInstanceUID); if (!displaySet) { @@ -148,14 +148,16 @@ export default class DisplaySetService extends PubSubService { } // broadcast event to update listeners with the new displaySets - this._broadcastEvent( - EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, - displaySetInstanceUID - ); + this._broadcastEvent(EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, { + displaySetInstanceUID, + invalidateData, + }); } public deleteDisplaySet(displaySetInstanceUID) { - if (!displaySetInstanceUID) return; + if (!displaySetInstanceUID) { + return; + } const { activeDisplaySets, activeDisplaySetsMap } = this; const activeDisplaySetsIndex = activeDisplaySets.findIndex( @@ -176,8 +178,15 @@ export default class DisplaySetService extends PubSubService { * @param {string} displaySetInstanceUID * @returns {object} displaySet */ - public getDisplaySetByUID = (displaySetInstanceUid: string): DisplaySet => - displaySetCache.get(displaySetInstanceUid); + public getDisplaySetByUID = (displaySetInstanceUid: string): DisplaySet => { + if (typeof displaySetInstanceUid !== 'string') { + throw new Error( + `getDisplaySetByUID: displaySetInstanceUid must be a string, you passed ${displaySetInstanceUid}` + ); + } + + return displaySetCache.get(displaySetInstanceUid); + }; /** * @@ -185,18 +194,13 @@ export default class DisplaySetService extends PubSubService { * @param {*} param1: settings: initialViewportSettings by HP or callbacks after rendering * @returns {string[]} - added displaySetInstanceUIDs */ - makeDisplaySets = ( - input, - { batch = false, madeInClient = false, settings = {} } = {} - ) => { + makeDisplaySets = (input, { batch = false, madeInClient = false, settings = {} } = {}) => { if (!input || !input.length) { throw new Error('No instances were provided.'); } if (batch && !input[0].length) { - throw new Error( - 'Batch displaySet creation does not contain array of array of instances.' - ); + throw new Error('Batch displaySet creation does not contain array of array of instances.'); } // If array of instances => One instance. @@ -205,10 +209,7 @@ export default class DisplaySetService extends PubSubService { if (batch) { for (let i = 0; i < input.length; i++) { const instances = input[i]; - const displaySets = this.makeDisplaySetForInstances( - instances, - settings - ); + const displaySets = this.makeDisplaySetForInstances(instances, settings); displaySetsAdded.push(...displaySets); } @@ -252,6 +253,41 @@ export default class DisplaySetService extends PubSubService { this.activeDisplaySetsMap.clear(); } + /** + * This function hides the old makeDisplaySetForInstances function to first + * separate the instances by sopClassUID so each call have only instances + * with the same sopClassUID, to avoid a series composed by different + * sopClassUIDs be filtered inside one of the SOPClassHandler functions and + * didn't appear in the series list. + * @param instancesSrc + * @param settings + * @returns + */ + public makeDisplaySetForInstances(instancesSrc: InstanceMetadata[], settings): DisplaySet[] { + // creating a sopClassUID list and for each sopClass associate its respective + // instance list + const instancesForSetSOPClasses = instancesSrc.reduce((sopClassList, instance) => { + if (!(instance.SOPClassUID in sopClassList)) { + sopClassList[instance.SOPClassUID] = []; + } + sopClassList[instance.SOPClassUID].push(instance); + return sopClassList; + }, {}); + // for each sopClassUID, call the old makeDisplaySetForInstances with a + // instance list composed only by instances with the same sopClassUID and + // accumulate the displaySets in the variable allDisplaySets + const sopClasses = Object.keys(instancesForSetSOPClasses); + let allDisplaySets = []; + sopClasses.forEach(sopClass => { + const displaySets = this._makeDisplaySetForInstances( + instancesForSetSOPClasses[sopClass], + settings + ); + allDisplaySets = [...allDisplaySets, ...displaySets]; + }); + return allDisplaySets; + } + /** * Creates new display sets for the instances contained in instancesSrc * according to the sop class handlers registered. @@ -268,17 +304,13 @@ export default class DisplaySetService extends PubSubService { * @param settings are settings to add * @returns Array of the display sets added. */ - public makeDisplaySetForInstances( - instancesSrc: InstanceMetadata[], - settings - ): DisplaySet[] { + private _makeDisplaySetForInstances(instancesSrc: InstanceMetadata[], settings): DisplaySet[] { // Some of the sop class handlers take a direct reference to instances // so make sure it gets copied here so that they have their own ref let instances = [...instancesSrc]; const instance = instances[0]; - const existingDisplaySets = - this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || []; + const existingDisplaySets = this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || []; const SOPClassHandlerIds = this.SOPClassHandlerIds; const allDisplaySets = []; @@ -306,13 +338,13 @@ export default class DisplaySetService extends PubSubService { this.activeDisplaySetsChanged = true; instances = filterInstances(instances, [addedDs]); this._addActiveDisplaySets([addedDs]); - this.setDisplaySetMetadataInvalidated( - addedDs.displaySetInstanceUID - ); + this.setDisplaySetMetadataInvalidated(addedDs.displaySetInstanceUID); } // This means that all instances already existed or got added to // existing display sets, and had an invalidated event fired - if (!instances.length) return allDisplaySets; + if (!instances.length) { + return allDisplaySets; + } } if (!instances.length) { @@ -327,7 +359,9 @@ export default class DisplaySetService extends PubSubService { // creating additional display sets using the sop class handler displaySets = handler.getDisplaySetsFromSeries(instances); - if (!displaySets || !displaySets.length) continue; + if (!displaySets || !displaySets.length) { + continue; + } // applying hp-defined viewport settings to the displaysets displaySets.forEach(ds => { @@ -347,6 +381,24 @@ export default class DisplaySetService extends PubSubService { allDisplaySets.push(...displaySets); } } + // applying the default sopClassUID handler + if (allDisplaySets.length === 0) { + // applying hp-defined viewport settings to the displaysets + const handler = this.extensionManager.getModuleEntry(this.unsuportedSOPClassHandler); + const displaySets = handler.getDisplaySetsFromSeries(instances); + if (displaySets?.length) { + displaySets.forEach(ds => { + Object.keys(settings).forEach(key => { + ds[key] = settings[key]; + }); + }); + + this._addDisplaySetsToCache(displaySets); + this._addActiveDisplaySets(displaySets); + + allDisplaySets.push(...displaySets); + } + } return allDisplaySets; } diff --git a/platform/core/src/services/DisplaySetService/IDisplaySet.ts b/platform/core/src/services/DisplaySetService/IDisplaySet.ts index 9b81d6f891e..f193e32221e 100644 --- a/platform/core/src/services/DisplaySetService/IDisplaySet.ts +++ b/platform/core/src/services/DisplaySetService/IDisplaySet.ts @@ -3,6 +3,7 @@ interface IDisplaySet { StudyInstanceUID: string; SeriesInstanceUID?: string; SeriesNumber?: string; + unsupported?: boolean; } export default IDisplaySet; diff --git a/platform/core/src/services/DisplaySetService/index.ts b/platform/core/src/services/DisplaySetService/index.ts index 6039e648c37..5893d7beea5 100644 --- a/platform/core/src/services/DisplaySetService/index.ts +++ b/platform/core/src/services/DisplaySetService/index.ts @@ -1,3 +1,5 @@ import DisplaySetService from './DisplaySetService'; +import { DisplaySetMessage, DisplaySetMessageList } from './DisplaySetMessage'; export default DisplaySetService; +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/services/HangingProtocolService/HPMatcher.js b/platform/core/src/services/HangingProtocolService/HPMatcher.js index b87c31e84ee..9d255cc9cac 100644 --- a/platform/core/src/services/HangingProtocolService/HPMatcher.js +++ b/platform/core/src/services/HangingProtocolService/HPMatcher.js @@ -9,12 +9,7 @@ import validate from './lib/validator'; * @param {object[]} options.displaySets is a list of the display sets * @return {Object} Matching Object with score and details (which rule passed or failed) */ -const match = ( - metadataInstance, - rules = [], - customAttributeRetrievalCallbacks, - options -) => { +const match = (metadataInstance, rules = [], customAttributeRetrievalCallbacks, options) => { const validateOptions = { format: 'grouped', }; @@ -46,12 +41,13 @@ const match = ( const { attribute, from = 'metadataInstance' } = rule; // Do not use the custom attribute from the metadataInstance since it is subject to change if (customAttributeRetrievalCallbacks.hasOwnProperty(attribute)) { - readValues[attribute] = customAttributeRetrievalCallbacks[ - attribute - ].callback.call(rule, metadataInstance, options); + readValues[attribute] = customAttributeRetrievalCallbacks[attribute].callback.call( + rule, + metadataInstance, + options + ); } else { - readValues[attribute] = - fromSrc[from]?.[attribute] ?? instance?.[attribute]; + readValues[attribute] = fromSrc[from]?.[attribute] ?? instance?.[attribute]; } // Format the constraint as required by Validate.js @@ -74,13 +70,14 @@ const match = ( errorMessages = ['Something went wrong during validation.', e]; } - console.log( - 'Test', - `${from}.${attribute}`, - readValues[attribute], - JSON.stringify(rule.constraint), - !errorMessages - ); + // TODO: move to a logger + // console.log( + // 'Test', + // `${from}.${attribute}`, + // readValues[attribute], + // JSON.stringify(rule.constraint), + // !errorMessages + // ); if (!errorMessages) { // If no errorMessages were returned, then validation passed. diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js index 1af19950b16..aa9dfa169f0 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js @@ -2,7 +2,6 @@ import HangingProtocolService from './HangingProtocolService'; const testProtocol = { id: 'test', - hasUpdatedPriorsInformation: false, name: 'Default', protocolMatchingRules: [ { @@ -119,7 +118,7 @@ function checkHpsBestMatch(hps) { hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets }); const { viewportMatchDetails } = hps.getMatchDetails(); expect(viewportMatchDetails.size).toBe(1); - expect(viewportMatchDetails.get(0)).toMatchObject({ + expect(viewportMatchDetails.get('ctAXIAL')).toMatchObject({ viewportOptions: { viewportId: 'ctAXIAL', viewportType: 'volume', @@ -134,7 +133,7 @@ function checkHpsBestMatch(hps) { displaySetOptions: { id: 'displaySetSelector', options: {}, - }, + }, }, ], }); @@ -150,10 +149,7 @@ describe('HangingProtocolService', () => { }, }, }; - const hangingProtocolService = new HangingProtocolService( - commandsManager, - servicesManager - ); + const hangingProtocolService = new HangingProtocolService(commandsManager, servicesManager); let initialScaling; afterEach(() => { @@ -178,10 +174,7 @@ describe('HangingProtocolService', () => { describe('with protocol generator', () => { beforeAll(() => { - hangingProtocolService.addProtocol( - testProtocol.id, - testProtocolGenerator - ); + hangingProtocolService.addProtocol(testProtocol.id, testProtocolGenerator); }); it('has one protocol', () => { @@ -192,6 +185,6 @@ describe('HangingProtocolService', () => { it('matches best image match', () => { checkHpsBestMatch(hangingProtocolService); }); - }); }); }); +}); diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts index 4d3f72df093..01fdd8f6ef0 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts @@ -8,12 +8,10 @@ import IDisplaySet from '../DisplaySetService/IDisplaySet'; import { CommandsManager } from '../../classes'; import ServicesManager from '../ServicesManager'; import * as HangingProtocol from '../../types/HangingProtocol'; -import { - isDisplaySetFromUrl, - sopInstanceLocation, -} from './custom-attribute/isDisplaySetFromUrl'; +import { isDisplaySetFromUrl, sopInstanceLocation } from './custom-attribute/isDisplaySetFromUrl'; import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages'; import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets'; +import uuidv4 from '../../utils/uuidv4'; type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator; @@ -36,8 +34,7 @@ export default class HangingProtocolService extends PubSubService { // Fired when the stages within the current protocol are known to have // the status set - that is, they are activated (or deactivated). STAGE_ACTIVATION: 'event::hanging_protocol_stage_activation', - CUSTOM_IMAGE_LOAD_PERFORMED: - 'event::hanging_protocol_custom_image_load_performed', + CUSTOM_IMAGE_LOAD_PERFORMED: 'event::hanging_protocol_custom_image_load_performed', }; public static REGISTRATION = { @@ -73,8 +70,7 @@ export default class HangingProtocolService extends PubSubService { customAttributeRetrievalCallbacks = { NumberOfStudyRelatedSeries: { name: 'The number of series in the study', - callback: metadata => - metadata.NumberOfStudyRelatedSeries ?? metadata.series?.length, + callback: metadata => metadata.NumberOfStudyRelatedSeries ?? metadata.series?.length, }, NumberOfSeriesRelatedInstances: { name: 'The number of instances in the display set', @@ -86,7 +82,9 @@ export default class HangingProtocolService extends PubSubService { metadata.ModalitiesInStudy ?? (metadata.series || []).reduce((prev, curr) => { const { Modality } = curr; - if (Modality && prev.indexOf(Modality) == -1) prev.push(Modality); + if (Modality && prev.indexOf(Modality) == -1) { + prev.push(Modality); + } return prev; }, []), }, @@ -130,11 +128,11 @@ export default class HangingProtocolService extends PubSubService { > = new Map(); /** - * An array that contains for each viewport (viewportIndex) specified in the + * An array that contains for each viewport (viewportId) specified in the * hanging protocol, an object of the form */ viewportMatchDetails: Map< - number, // viewportIndex + string, // viewportId HangingProtocol.ViewportMatchDetails > = new Map(); @@ -159,6 +157,9 @@ export default class HangingProtocolService extends PubSubService { this.studies = []; this.viewportMatchDetails = new Map(); this.displaySetMatchDetails = new Map(); + this.protocol = undefined; + this.stageIndex = undefined; + this.protocolEngine = undefined; } /** Leave the hanging protocol in the initialized state */ @@ -177,15 +178,17 @@ export default class HangingProtocolService extends PubSubService { */ public getActiveProtocol(): { protocol: HangingProtocol.Protocol; + _originalProtocol: HangingProtocol.Protocol; stage: HangingProtocol.ProtocolStage; stageIndex: number; activeStudy?: StudyMetadata; - viewportMatchDetails: Map; + viewportMatchDetails: Map; displaySetMatchDetails: Map; activeImageLoadStrategyName: string; } { return { protocol: this.protocol, + _originalProtocol: this._originalProtocol, stage: this.protocol?.stages?.[this.stageIndex], stageIndex: this.stageIndex, activeStudy: this.activeStudy, @@ -200,7 +203,9 @@ export default class HangingProtocolService extends PubSubService { * protocolId, stageIndex, stageId and activeStudyUID */ public getState(): HangingProtocol.HPInfo { - if (!this.protocol) return; + if (!this.protocol) { + return; + } return { protocolId: this.protocol.id, stageIndex: this.stageIndex, @@ -257,8 +262,12 @@ export default class HangingProtocolService extends PubSubService { * @returns protocol - the protocol with the given id */ public getProtocolById(protocolId: string): HangingProtocol.Protocol { - if (!protocolId) return; - if (protocolId === this.protocol?.id) return this.protocol; + if (!protocolId) { + return; + } + if (protocolId === this.protocol?.id) { + return this.protocol; + } const protocol = this.protocols.get(protocolId); if (!protocol) { throw new Error(`No protocol ${protocolId} found`); @@ -266,9 +275,7 @@ export default class HangingProtocolService extends PubSubService { if (protocol instanceof Function) { try { - const { protocol: generatedProtocol } = this._getProtocolFromGenerator( - protocol - ); + const { protocol: generatedProtocol } = this._getProtocolFromGenerator(protocol); return generatedProtocol; } catch (error) { @@ -292,9 +299,7 @@ export default class HangingProtocolService extends PubSubService { */ public addProtocol(protocolId: string, protocol: Protocol): void { if (this.protocols.has(protocolId)) { - console.warn( - `A protocol with id ${protocolId} already exists. It will be overwritten.` - ); + console.warn(`A protocol with id ${protocolId} already exists. It will be overwritten.`); } if (!(protocol instanceof Function)) { @@ -344,9 +349,7 @@ export default class HangingProtocolService extends PubSubService { * specifically, but will show another study instead. */ public setActiveStudyUID(activeStudyUID: string): void { - this.activeStudy = this.studies.find( - it => it.StudyInstanceUID === activeStudyUID - ); + this.activeStudy = this.studies.find(it => it.StudyInstanceUID === activeStudyUID); } /** @@ -361,7 +364,7 @@ export default class HangingProtocolService extends PubSubService { * @param params is the dataset to run the hanging protocol on. * @param params.activeStudy is the "primary" study to hang This may or may * not be displayed by the actual viewports. - * @param params.studies is the list of studies to hang. If absent, will re-use the previous set. + * @param params.studies is the list of studies to hang. If absent, will reuse the previous set. * @param params.displaySets is the list of display sets associated with * the studies to display in viewports. * @param protocol is a specific protocol to apply. @@ -393,21 +396,38 @@ export default class HangingProtocolService extends PubSubService { /** * Returns true, if the hangingProtocol has a custom loading strategy for the images * and its callback has been added to the HangingProtocolService - * @returns {boolean} true + * @returns A boolean indicating whether a custom image load strategy has been added or not. */ public hasCustomImageLoadStrategy(): boolean { return ( this.activeImageLoadStrategyName !== null && - this.registeredImageLoadStrategies[ - this.activeImageLoadStrategyName - ] instanceof Function + this.registeredImageLoadStrategies[this.activeImageLoadStrategyName] instanceof Function ); } + /** + * Returns a boolean indicating whether a custom image load has been performed or not. + * A custom image load is performed when a custom image load strategy is used to load images. + * This method is used internally by the HangingProtocolService to determine whether to perform + * a custom image load or not. + * + * @returns A boolean indicating whether a custom image load has been performed or not. + */ public getCustomImageLoadPerformed(): boolean { return this.customImageLoadPerformed; } + /** + * Returns a boolean indicating whether a custom image load should be performed or not. + * A custom image load should be performed if a custom image load strategy has been added to the HangingProtocolService + * and it has not been performed yet. + * + * @returns A boolean indicating whether a custom image load should be performed or not. + */ + public getShouldPerformCustomImageLoad(): boolean { + return this.hasCustomImageLoadStrategy() && !this.getCustomImageLoadPerformed(); + } + /** * Set the strategy callback for loading images to the HangingProtocolService * @param {string} name strategy name @@ -431,10 +451,7 @@ export default class HangingProtocolService extends PubSubService { public addCustomAttribute( attributeId: string, attributeName: string, - callback: ( - metadata: Record, - extraData?: Record - ) => unknown, + callback: (metadata: Record, extraData?: Record) => unknown, options: Record = {} ): void { this.customAttributeRetrievalCallbacks[attributeId] = { @@ -450,9 +467,7 @@ export default class HangingProtocolService extends PubSubService { * if no strategy is set, the default strategy is used */ runImageLoadStrategy(data): boolean { - const loader = this.registeredImageLoadStrategies[ - this.activeImageLoadStrategyName - ]; + const loader = this.registeredImageLoadStrategies[this.activeImageLoadStrategyName]; const loadedData = loader({ data, displaySetsMatchDetails: this.displaySetMatchDetails, @@ -471,9 +486,7 @@ export default class HangingProtocolService extends PubSubService { return true; } - _validateProtocol( - protocol: HangingProtocol.Protocol - ): HangingProtocol.Protocol { + _validateProtocol(protocol: HangingProtocol.Protocol): HangingProtocol.Protocol { protocol.id = protocol.id || protocol.name; const defaultViewportOptions = { toolGroupId: 'default', @@ -508,21 +521,38 @@ export default class HangingProtocolService extends PubSubService { for (let i = 0; i < rows * columns; i++) { stage.viewports.push({ - viewportOptions: defaultViewportOptions, + viewportOptions: { + ...defaultViewportOptions, + // Use 'default' for the first viewport, and UUIDs for the rest. + viewportId: i === 0 ? 'default' : uuidv4(), + }, displaySets: [], }); } } else { + // Clone each viewport to ensure independent objects + stage.viewports = stage.viewports.map((viewport, index) => { + const existingViewportId = viewport.viewportOptions?.viewportId; + + return { + ...viewport, + viewportOptions: { + ...(viewport.viewportOptions || defaultViewportOptions), + // use provided viewportId when available, otherwise use default for first viewport + // and uuid for the rest + viewportId: existingViewportId + ? existingViewportId + : index === 0 + ? 'default' + : uuidv4(), + }, + displaySets: viewport.displaySets || [], + }; + }); stage.viewports.forEach(viewport => { - viewport.viewportOptions = - viewport.viewportOptions || defaultViewportOptions; - if (!viewport.displaySets) { - viewport.displaySets = []; - } else { - viewport.displaySets.forEach(displaySet => { - displaySet.options = displaySet.options || {}; - }); - } + viewport.displaySets.forEach(displaySet => { + displaySet.options = displaySet.options || {}; + }); }); } }); @@ -530,9 +560,7 @@ export default class HangingProtocolService extends PubSubService { return protocol; } - private _getProtocolFromGenerator( - protocolGenerator: HangingProtocol.ProtocolGenerator - ): { + private _getProtocolFromGenerator(protocolGenerator: HangingProtocol.ProtocolGenerator): { protocol: HangingProtocol.Protocol; } { const { protocol } = protocolGenerator({ @@ -547,20 +575,32 @@ export default class HangingProtocolService extends PubSubService { }; } - getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) { + getViewportsRequireUpdate(viewportId, displaySetInstanceUID) { + const { displaySetService } = this._servicesManager.services; + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (displaySet?.unsupported) { + throw new Error('Unsupported displaySet'); + } const newDisplaySetInstanceUID = displaySetInstanceUID; const protocol = this.protocol; const protocolStage = protocol.stages[this.stageIndex]; const protocolViewports = protocolStage.viewports; - const protocolViewport = protocolViewports[viewportIndex]; const defaultReturn = [ { - viewportIndex, + viewportId, displaySetInstanceUIDs: [newDisplaySetInstanceUID], }, ]; + if (!protocolViewports) { + return defaultReturn; + } + + const protocolViewport = protocolViewports.find( + pv => pv.viewportOptions.viewportId === viewportId + ); + // if no viewport, then we can assume there is no predefined set of // rules that should be applied to this viewport while matching if (!protocolViewport) { @@ -589,12 +629,9 @@ export default class HangingProtocolService extends PubSubService { // if the viewport is not empty, then we check the displaySets it is showing // currently, which means we need to check if the requested updated displaySet // follow the same rules as the current displaySets - const { - id: displaySetSelectorId, - matchedDisplaySetsIndex = 0, - } = protocolViewport.displaySets[0]; - const displaySetSelector = - protocol.displaySetSelectors[displaySetSelectorId]; + const { id: displaySetSelectorId, matchedDisplaySetsIndex = 0 } = + protocolViewport.displaySets[0]; + const displaySetSelector = protocol.displaySetSelectors[displaySetSelectorId]; if (!displaySetSelector) { return defaultReturn; @@ -626,13 +663,13 @@ export default class HangingProtocolService extends PubSubService { } // if we reach here, it means that the displaySetInstanceUIDs to be dropped - // in the viewportIndex are valid, and we can proceed with the update. However + // for the desired viewportId are valid, and we can proceed with the update. However // we need to check if the displaySets that the viewport were showing // was also referenced by other viewports, and if so, we need to update those // viewports as well // check if displaySetSelectors are used by other viewports, and - // store the viewportIndex and displaySetInstanceUIDs that need to be updated + // store the viewportId and displaySetInstanceUIDs that need to be updated const viewportsToUpdate = []; protocolViewports.forEach((viewport, index) => { const viewportNeedsUpdate = viewport.displaySets.some( @@ -643,22 +680,20 @@ export default class HangingProtocolService extends PubSubService { if (viewportNeedsUpdate) { // Try to recompute the viewport options based on the current - // viewportIndex that needs update but from its old/original un-computed + // viewportId that needs update but from its old/original un-computed // viewport & displaySet options if (originalProtocolStage) { const originalViewport = originalProtocolStage.viewports[index]; const originalViewportOptions = originalViewport.viewportOptions; const originalDisplaySetOptions = originalViewport.displaySets; - viewport.viewportOptions = this.getComputedOptions( - originalViewportOptions, - [newDisplaySetInstanceUID] - ); + viewport.viewportOptions = this.getComputedOptions(originalViewportOptions, [ + newDisplaySetInstanceUID, + ]); - viewport.displaySets = this.getComputedOptions( - originalDisplaySetOptions, - [newDisplaySetInstanceUID] - ); + viewport.displaySets = this.getComputedOptions(originalDisplaySetOptions, [ + newDisplaySetInstanceUID, + ]); } const displaySetInstanceUIDs = []; @@ -674,7 +709,7 @@ export default class HangingProtocolService extends PubSubService { ); viewportsToUpdate.push({ - viewportIndex: index, + viewportId: viewport.viewportOptions.viewportId, displaySetInstanceUIDs, viewportOptions: viewport.viewportOptions, displaySetOptions, @@ -695,15 +730,18 @@ export default class HangingProtocolService extends PubSubService { ) { viewport.displaySets.forEach(displaySet => { const { id } = displaySet; - const { - displaySetInstanceUID: oldDisplaySetInstanceUID, - } = displaySetMatchDetails.get(id); + const displaySetMatchDetail = displaySetMatchDetails.get(id); - displaySetInstanceUIDs.push( + const { displaySetInstanceUID: oldDisplaySetInstanceUID } = displaySetMatchDetail; + + const displaySetInstanceUID = displaySet.id === displaySetSelectorId ? newDisplaySetInstanceUID - : oldDisplaySetInstanceUID - ); + : oldDisplaySetInstanceUID; + + displaySetMatchDetail.displaySetInstanceUID = displaySetInstanceUID; + + displaySetInstanceUIDs.push(displaySetInstanceUID); displaySetOptions.push(displaySet); }); } @@ -722,13 +760,15 @@ export default class HangingProtocolService extends PubSubService { ): any { // Base case: if options is an array, map over the array and recursively call getComputedOptions if (Array.isArray(options)) { - return options.map(option => - this.getComputedOptions(option, displaySetUIDs) - ); + return options.map(option => this.getComputedOptions(option, displaySetUIDs)); } - if (options === null) return options; - if (typeof options !== 'object') return options; + if (options === null) { + return options; + } + if (typeof options !== 'object') { + return options; + } // If options is an object with a custom attribute, compute a new options object if (options.custom) { @@ -743,8 +783,7 @@ export default class HangingProtocolService extends PubSubService { ); } - const callback = this.customAttributeRetrievalCallbacks[customKey] - .callback; + const callback = this.customAttributeRetrievalCallbacks[customKey].callback; let newOptions = callback.call(options, displaySets); if (newOptions === undefined) { @@ -772,7 +811,7 @@ export default class HangingProtocolService extends PubSubService { * @param protocolId - name of the registered protocol to be set * @param options - options to be passed to the protocol, this is either an array * of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object - * that contains viewportIndex as the key and displaySetInstanceUIDs as the value + * that contains viewportId as the key and displaySetInstanceUIDs as the value * for each viewport that needs to be set. * @param errorCallback - callback to be called if there is an error * during the protocol application @@ -849,18 +888,12 @@ export default class HangingProtocolService extends PubSubService { * * @returns the stage number to apply initially, given the options. */ - private _updateStageStatus( - options = null as HangingProtocol.SetProtocolOptions - ) { + private _updateStageStatus(options = null as HangingProtocol.SetProtocolOptions) { const stages = this.protocol.stages; for (let i = 0; i < stages.length; i++) { const stage = stages[i]; - const { matchedViewports } = this._matchAllViewports( - stage, - options, - new Map() - ); + const { matchedViewports } = this._matchAllViewports(stage, options, new Map()); const activation = stage.stageActivation || {}; if (this.matchActivation(matchedViewports, activation.passive, 0)) { if (this.matchActivation(matchedViewports, activation.enabled, 1)) { @@ -879,9 +912,7 @@ export default class HangingProtocolService extends PubSubService { }); } - private _findStageIndex( - options = null as HangingProtocol.SetProtocolOptions - ): number | void { + private _findStageIndex(options = null as HangingProtocol.SetProtocolOptions): number | void { const stageId = options?.stageId; const protocol = this.protocol; const stages = protocol.stages; @@ -889,7 +920,9 @@ export default class HangingProtocolService extends PubSubService { if (stageId) { for (let i = 0; i < stages.length; i++) { const stage = stages[i]; - if (stage.id === stageId && stage.status !== 'disabled') return i; + if (stage.id === stageId && stage.status !== 'disabled') { + return i; + } } return; } @@ -902,7 +935,9 @@ export default class HangingProtocolService extends PubSubService { let firstNotDisabled: number; for (let i = 0; i < stages.length; i++) { - if (stages[i].status === 'enabled') return i; + if (stages[i].status === 'enabled') { + return i; + } if (firstNotDisabled === undefined && stages[i].status !== 'disabled') { firstNotDisabled = i; } @@ -926,10 +961,7 @@ export default class HangingProtocolService extends PubSubService { const { imageLoadStrategy } = protocol; if (imageLoadStrategy) { // check if the imageLoadStrategy is a valid strategy - if ( - this.registeredImageLoadStrategies[imageLoadStrategy] instanceof - Function - ) { + if (this.registeredImageLoadStrategies[imageLoadStrategy] instanceof Function) { this.activeImageLoadStrategyName = imageLoadStrategy; } } @@ -939,9 +971,7 @@ export default class HangingProtocolService extends PubSubService { const stage = this._findStageIndex(options); if (stage === undefined) { - throw new Error( - `Can't find applicable stage ${protocol.id} ${options?.stageIndex}` - ); + throw new Error(`Can't find applicable stage ${protocol.id} ${options?.stageIndex}`); } this.stageIndex = stage as number; this._updateViewports(options); @@ -976,7 +1006,9 @@ export default class HangingProtocolService extends PubSubService { if (stageId !== undefined) { return protocol.stages.findIndex(it => it.id === stageId); } - if (stageIndex !== undefined) return stageIndex; + if (stageIndex !== undefined) { + return stageIndex; + } return 0; } @@ -985,11 +1017,7 @@ export default class HangingProtocolService extends PubSubService { * undefined if no protocol or stages are set */ _getNumProtocolStages() { - if ( - !this.protocol || - !this.protocol.stages || - !this.protocol.stages.length - ) { + if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) { return; } @@ -1027,7 +1055,9 @@ export default class HangingProtocolService extends PubSubService { const protocol = this.protocol; const stage = protocol.stages[stageIdx]; const defaultViewport = stage.defaultViewport || protocol.defaultViewport; - if (!defaultViewport) return; + if (!defaultViewport) { + return; + } const useViewport = { ...defaultViewport }; return this._matchViewport(useViewport, options); @@ -1063,7 +1093,6 @@ export default class HangingProtocolService extends PubSubService { // matching applied this.viewportMatchDetails = new Map(); this.displaySetMatchDetails = new Map(); - this.customImageLoadPerformed = false; // Retrieve the current stage const stageModel = this._getCurrentStageModel(); @@ -1113,7 +1142,11 @@ export default class HangingProtocolService extends PubSubService { displaySetMatchDetails: Map; } { let matchedViewports = 0; - stageModel.viewports.forEach((viewport, viewportIndex) => { + stageModel.viewports.forEach(viewport => { + // Todo: we should probably assign a random viewportId if not defined + // below, but it feels odd since viewportGrid should handle this kind + // of thing + const viewportId = viewport.viewportOptions.viewportId; const matchDetails = this._matchViewport( viewport, options, @@ -1127,14 +1160,12 @@ export default class HangingProtocolService extends PubSubService { ) { matchedViewports++; } else { - console.log( - 'Adding an empty set of display sets for mapping purposes' - ); + console.log('Adding an empty set of display sets for mapping purposes'); matchDetails.displaySetsInfo = viewport.displaySets.map(it => ({ displaySetOptions: it, })); } - viewportMatchDetails.set(viewportIndex, matchDetails); + viewportMatchDetails.set(viewportId, matchDetails); } }); return { @@ -1149,18 +1180,20 @@ export default class HangingProtocolService extends PubSubService { offset: number, options: HangingProtocol.SetProtocolOptions = {} ): HangingProtocol.DisplaySetMatchDetails { - if (!matchDetails) return; - if (offset === 0) return matchDetails; + if (!matchDetails) { + return; + } + if (offset === 0) { + return matchDetails; + } const { matchingScores = [] } = matchDetails; if (offset === -1) { const { inDisplay } = options; - if (!inDisplay) return matchDetails; + if (!inDisplay) { + return matchDetails; + } for (let i = 0; i < matchDetails.matchingScores.length; i++) { - if ( - inDisplay.indexOf( - matchDetails.matchingScores[i].displaySetInstanceUID - ) === -1 - ) { + if (inDisplay.indexOf(matchDetails.matchingScores[i].displaySetInstanceUID) === -1) { const match = matchDetails.matchingScores[i]; return match.matchingScore > 0 ? { @@ -1181,16 +1214,18 @@ export default class HangingProtocolService extends PubSubService { id: string, displaySetUID: string ): void { - if (match.displaySetInstanceUID === displaySetUID) return; + if (match.displaySetInstanceUID === displaySetUID) { + return; + } if (!match.matchingScores) { throw new Error('No matchingScores found in ' + match); } for (const subMatch of match.matchingScores) { - if (subMatch.displaySetInstanceUID === displaySetUID) return; + if (subMatch.displaySetInstanceUID === displaySetUID) { + return; + } } - throw new Error( - `Reused viewport details ${id} with ds ${displaySetUID} not valid` - ); + throw new Error(`Reused viewport details ${id} with ds ${displaySetUID} not valid`); } protected _matchViewport( @@ -1212,9 +1247,7 @@ export default class HangingProtocolService extends PubSubService { console.warn('No display set selector for', displaySetId); continue; } - const { bestMatch, matchingScores } = this._matchImages( - displaySetSelector - ); + const { bestMatch, matchingScores } = this._matchImages(displaySetSelector); displaySetMatchDetails.set(displaySetId, bestMatch); if (bestMatch) { @@ -1231,10 +1264,7 @@ export default class HangingProtocolService extends PubSubService { viewport.displaySets.forEach(displaySetOptions => { const { id, matchedDisplaySetsIndex = 0 } = displaySetOptions; const reuseDisplaySetUID = - id && - displaySetSelectorMap[ - `${activeStudyUID}:${id}:${matchedDisplaySetsIndex || 0}` - ]; + id && displaySetSelectorMap[`${activeStudyUID}:${id}:${matchedDisplaySetsIndex || 0}`]; const viewportDisplaySetMain = this.displaySetMatchDetails.get(id); const viewportDisplaySet = this.findDeduplicatedMatchDetails( @@ -1246,11 +1276,7 @@ export default class HangingProtocolService extends PubSubService { // Use the display set provided instead if (reuseDisplaySetUID) { if (viewportOptions.allowUnmatchedView !== true) { - this.validateDisplaySetSelectMatch( - viewportDisplaySet, - id, - reuseDisplaySetUID - ); + this.validateDisplaySetSelectMatch(viewportDisplaySet, id, reuseDisplaySetUID); } const displaySetInfo: HangingProtocol.DisplaySetInfo = { displaySetInstanceUID: reuseDisplaySetUID, @@ -1294,8 +1320,7 @@ export default class HangingProtocolService extends PubSubService { ): void { const { displaySetService } = this._servicesManager.services; const protocolViewportDisplaySets = protocolViewport.displaySets; - const numDisplaySetsToSet = - displaySetAndViewportOptions.displaySetInstanceUIDs.length; + const numDisplaySetsToSet = displaySetAndViewportOptions.displaySetInstanceUIDs.length; if ( protocolViewportDisplaySets.length > 0 && @@ -1306,24 +1331,20 @@ export default class HangingProtocolService extends PubSubService { ); } - displaySetAndViewportOptions.displaySetInstanceUIDs.forEach( - displaySetInstanceUID => { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + displaySetAndViewportOptions.displaySetInstanceUIDs.forEach(displaySetInstanceUID => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - const { displaySets: displaySetsInfo } = protocolViewport; + const { displaySets: displaySetsInfo } = protocolViewport; - for (const displaySetInfo of displaySetsInfo) { - const displaySetSelector = displaySetSelectors[displaySetInfo.id]; + for (const displaySetInfo of displaySetsInfo) { + const displaySetSelector = displaySetSelectors[displaySetInfo.id]; - if (!displaySetSelector) { - continue; - } - this._validateRequiredSelectors(displaySetSelector, displaySet); + if (!displaySetSelector) { + continue; } + this._validateRequiredSelectors(displaySetSelector, displaySet); } - ); + }); } private _validateRequiredSelectors( @@ -1349,19 +1370,15 @@ export default class HangingProtocolService extends PubSubService { const { displaySetService } = this._servicesManager.services; const { displaySetSelectorMap } = options; if (displaySetSelectorMap) { - Object.entries(displaySetSelectorMap).forEach( - ([key, displaySetInstanceUID]) => { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + Object.entries(displaySetSelectorMap).forEach(([key, displaySetInstanceUID]) => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - if (!displaySet) { - throw new Error( - `The displaySetInstanceUID ${displaySetInstanceUID} is not found in the displaySetService` - ); - } + if (!displaySet) { + throw new Error( + `The displaySetInstanceUID ${displaySetInstanceUID} is not found in the displaySetService` + ); } - ); + }); } } @@ -1371,54 +1388,39 @@ export default class HangingProtocolService extends PubSubService { // level matching needs to be added in future // Todo: handle fusion viewports by not taking the first displaySet rule for the viewport - const { - id, - studyMatchingRules = [], - seriesMatchingRules, - } = displaySetRules; + const { id, studyMatchingRules = [], seriesMatchingRules } = displaySetRules; const matchingScores = []; let highestSeriesMatchingScore = 0; - console.log( - 'ProtocolEngine::matchImages', - studyMatchingRules, - seriesMatchingRules - ); + console.log('ProtocolEngine::matchImages', studyMatchingRules, seriesMatchingRules); const matchActiveOnly = this.protocol.numberOfPriorsReferenced === -1; - this.studies.forEach(study => { + this.studies.forEach((study, studyInstanceUIDsIndex) => { // Skip non-active if active only - if (matchActiveOnly && this.activeStudy !== study) return; + if (matchActiveOnly && this.activeStudy !== study) { + return; + } + const studyDisplaySets = this.displaySets.filter( - it => it.StudyInstanceUID === study.StudyInstanceUID - ); - const studyMatchDetails = this.protocolEngine.findMatch( - study, - studyMatchingRules, - { - studies: this.studies, - displaySets: studyDisplaySets, - displaySetMatchDetails: this.displaySetMatchDetails, - } + it => it.StudyInstanceUID === study.StudyInstanceUID && !it?.unsupported ); + const studyMatchDetails = this.protocolEngine.findMatch(study, studyMatchingRules, { + studies: this.studies, + displaySets: studyDisplaySets, + allDisplaySets: this.displaySets, + displaySetMatchDetails: this.displaySetMatchDetails, + studyInstanceUIDsIndex, + }); + // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed if (studyMatchDetails.requiredFailed === true) { return; } - this.debug( - 'study', - study.StudyInstanceUID, - 'display sets #', - studyDisplaySets.length - ); + this.debug('study', study.StudyInstanceUID, 'display sets #', studyDisplaySets.length); studyDisplaySets.forEach(displaySet => { - const { - StudyInstanceUID, - SeriesInstanceUID, - displaySetInstanceUID, - } = displaySet; + const { StudyInstanceUID, SeriesInstanceUID, displaySetInstanceUID } = displaySet; const seriesMatchDetails = this.protocolEngine.findMatch( displaySet, seriesMatchingRules, @@ -1433,41 +1435,25 @@ export default class HangingProtocolService extends PubSubService { // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed if (seriesMatchDetails.requiredFailed === true) { - this.debug( - 'Display set required failed', - displaySet, - seriesMatchingRules - ); + this.debug('Display set required failed', displaySet, seriesMatchingRules); return; } this.debug('Found displaySet for rules', displaySet); - highestSeriesMatchingScore = Math.max( - seriesMatchDetails.score, - highestSeriesMatchingScore - ); + highestSeriesMatchingScore = Math.max(seriesMatchDetails.score, highestSeriesMatchingScore); const matchDetails = { passed: [], failed: [], }; - matchDetails.passed = matchDetails.passed.concat( - seriesMatchDetails.details.passed - ); - matchDetails.passed = matchDetails.passed.concat( - studyMatchDetails.details.passed - ); + matchDetails.passed = matchDetails.passed.concat(seriesMatchDetails.details.passed); + matchDetails.passed = matchDetails.passed.concat(studyMatchDetails.details.passed); - matchDetails.failed = matchDetails.failed.concat( - seriesMatchDetails.details.failed - ); - matchDetails.failed = matchDetails.failed.concat( - studyMatchDetails.details.failed - ); + matchDetails.failed = matchDetails.failed.concat(seriesMatchDetails.details.failed); + matchDetails.failed = matchDetails.failed.concat(studyMatchDetails.details.failed); - const totalMatchScore = - seriesMatchDetails.score + studyMatchDetails.score; + const totalMatchScore = seriesMatchDetails.score + studyMatchDetails.score; const imageDetails = { StudyInstanceUID, @@ -1503,17 +1489,11 @@ export default class HangingProtocolService extends PubSubService { }, this._getSeriesFieldForDisplaySetSort() ); - matchingScores.sort((a, b) => - sortingFunction(a.sortingInfo, b.sortingInfo) - ); + matchingScores.sort((a, b) => sortingFunction(a.sortingInfo, b.sortingInfo)); const bestMatch = matchingScores[0]; - console.log( - 'ProtocolEngine::matchImages bestMatch', - bestMatch, - matchingScores - ); + console.log('ProtocolEngine::matchImages bestMatch', bestMatch, matchingScores); return { bestMatch, @@ -1582,9 +1562,7 @@ export default class HangingProtocolService extends PubSubService { this.stageIndex = i; // Log the new stage - this.debug( - `ProtocolEngine::setCurrentProtocolStage stage = ${this.stageIndex}` - ); + this.debug(`ProtocolEngine::setCurrentProtocolStage stage = ${this.stageIndex}`); // Since stage has changed, we need to update the viewports // and redo matchings diff --git a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js index b3c18ab40c7..40a2947674d 100644 --- a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js +++ b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js @@ -92,12 +92,7 @@ export default class ProtocolEngine { * @returns */ findMatch(metaData, rules, options) { - return HPMatcher.match( - metaData, - rules, - this.customAttributeRetrievalCallbacks, - options - ); + return HPMatcher.match(metaData, rules, this.customAttributeRetrievalCallbacks, options); } /** @@ -142,8 +137,7 @@ export default class ProtocolEngine { // if not select the first protocol in the list if (!matched.length) { const protocol = - this.protocols.find(protocol => protocol.id === 'default') ?? - this.protocols[0]; + this.protocols.find(protocol => protocol.id === 'default') ?? this.protocols[0]; console.log('No protocol matches, defaulting to', protocol); return [ { @@ -174,9 +168,7 @@ export default class ProtocolEngine { if (!Object.keys(this.matchedProtocolScores).length) { return; } - const highestScoringProtocolId = this._largestKeyByValue( - this.matchedProtocolScores - ); + const highestScoringProtocolId = this._largestKeyByValue(this.matchedProtocolScores); return this.matchedProtocols.get(highestScoringProtocolId); } } diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts index d1626919da9..20ebb76fe45 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts @@ -8,17 +8,14 @@ import { getSplitParam } from '../../../utils'; */ const isDisplaySetFromUrl = (displaySet): boolean => { const params = new URLSearchParams(window.location.search); - const initialSeriesInstanceUID = getSplitParam( - 'initialseriesinstanceuid', - params - ); + const initialSeriesInstanceUID = getSplitParam('initialseriesinstanceuid', params); const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid', params); - if (!initialSeriesInstanceUID && !initialSOPInstanceUID) return false; + if (!initialSeriesInstanceUID && !initialSOPInstanceUID) { + return false; + } const isSeriesMatch = !initialSeriesInstanceUID || - initialSeriesInstanceUID.some( - seriesUID => displaySet.SeriesInstanceUID === seriesUID - ); + initialSeriesInstanceUID.some(seriesUID => displaySet.SeriesInstanceUID === seriesUID); const isSopMatch = !initialSOPInstanceUID || displaySet.instances?.some?.(instance => @@ -32,9 +29,13 @@ const isDisplaySetFromUrl = (displaySet): boolean => { */ function sopInstanceLocation(displaySets) { const displaySet = displaySets?.[0]; - if (!displaySet) return; + if (!displaySet) { + return; + } const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid'); - if (!initialSOPInstanceUID) return; + if (!initialSOPInstanceUID) { + return; + } const index = displaySet.instances.findIndex(instance => initialSOPInstanceUID.includes(instance.SOPInstanceUID) diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts index 75c3d969c6e..d61ff3c62c7 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts @@ -1,5 +1,5 @@ export default (study, extraData) => { - const ret = extraData?.displaySets?.filter(ds => ds.numImageFrames>0)?.length; - console.log("number of display sets with images", ret); + const ret = extraData?.displaySets?.filter(ds => ds.numImageFrames > 0)?.length; + console.log('number of display sets with images', ret); return ret; -}; \ No newline at end of file +}; diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts index 445fb6dd218..cb32f1bbcda 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts @@ -1 +1 @@ -export default (study, extraData) => extraData?.displaySets?.map(ds => ds.SeriesDescription); \ No newline at end of file +export default (study, extraData) => extraData?.displaySets?.map(ds => ds.SeriesDescription); diff --git a/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js b/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js index 17368c87721..97f85be63eb 100644 --- a/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js +++ b/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js @@ -64,8 +64,7 @@ function displayConstraint(attributeId, constraint, attributes) { } const attributeText = getAttributeText(attributeId, attributes); - const constraintText = - attributeText + ' ' + humanize(comparator).toLowerCase() + ' ' + value; + const constraintText = attributeText + ' ' + humanize(comparator).toLowerCase() + ' ' + value; return constraintText; } diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.js b/platform/core/src/services/HangingProtocolService/lib/validator.js index 498ae3201ae..72c0a9549ca 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.js @@ -1,70 +1,224 @@ import validate from 'validate.js'; +/** + * check if the value is strictly equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Fail) + * = ['abc'] (Fail) + * = ['abc', 'def', 'GHI'] (Valid) + * = ['abc', 'GHI', 'def'] (Fail) + * = ['abc', 'def'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = 'Attenuation Corrected' (Valid) + * testValue = 'Attenuation' (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Valid) + * = 'Attenuation Corrected' (Valid) + * = 'Attenuation' (Fail) + * + * */ +validate.validators.equals = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + // If options is an array, then we need to validate each element in the array + if (Array.isArray(testValue)) { + // If the array has only one element, then we need to compare the value to that element + if (testValue.length !== dicomArrayValue.length) { + return `${key} must be an array of length ${testValue.length}`; + } else { + for (let i = 0; i < testValue.length; i++) { + if (testValue[i] !== dicomArrayValue[i]) { + return `${key} ${testValue[i]} must equal ${dicomArrayValue[i]}`; + } + } + } + } else if (testValue !== dicomArrayValue[0]) { + return `${key} must equal ${testValue}`; + } +}; +/** + * check if the value is not equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Valid) + * = ['abc'] (Valid) + * = ['abc', 'def', 'GHI'] (Fail) + * = ['abc', 'GHI', 'def'] (Valid) + * = ['abc', 'def'] (Valid) + * + * value = 'Attenuation Corrected' + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Valid) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Fail) + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Fail) + * */ +validate.validators.doesNotEqual = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); -validate.validators.equals = function(value, options, key, attributes) { - const testValue = options?.value ?? options; - if (value !== testValue) { - return key + 'must equal ' + testValue; + if (Array.isArray(testValue)) { + if (testValue.length === dicomArrayValue.length) { + let score = 0; + testValue.forEach((x, i) => { + if (x === dicomArrayValue[i]) { + score++; + } + }); + if (score === testValue.length) { + return `${key} must not equal to ${testValue}`; + } + } + } else if (testValue === dicomArrayValue[0]) { + console.log(dicomArrayValue, testValue); + return `${key} must not equal to ${testValue}`; } }; -validate.validators.doesNotEqual = function(value, options, key) { - const testValue = options?.value ?? options; - if (value === testValue) { - return key + 'cannot equal ' + testValue; +/** + * Check if a value includes one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ‘abc’ (Fail) + * = ‘dog’ (Fail) + * = [‘abc’] (Valid) + * = [‘att’, ‘abc’] (Valid) + * = ['abc', 'def', 'dog'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * */ +validate.validators.includes = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length === 0) { + return `${key} must include at least one of the following values: ${testValue.join(', ')}`; + } + } else { + return `${key} ${testValue} must be an array`; } + // else if (!value.includes(testValue)) { + // return `${key} ${value} must include ${testValue}`; + // } }; +/** + * Check if a value does not include one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ['Corr'] (Valid) + * = 'abc' (Fail) + * = ['abc'] (Fail) + * = [‘att’, ‘cor’] (Valid) + * = ['abc', 'def', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * */ +validate.validators.doesNotInclude = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + // if (!Array.isArray(value) || value.length === 1) { + // return `${key} is not allowed as a single value`; + // } + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length > 0) { + return `${key} must not include the following value: ${includedValues}`; + } + } else { + return `${key} ${testValue} must be an array`; + } +}; // Ignore case contains. // options testValue MUST be in lower case already, otherwise it won't match +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['gh', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ validate.validators.containsI = function (value, options, key) { - const testValue = options?.value ?? options; + const testValue = getTestValue(options); if (Array.isArray(value)) { - if ( - value.some( - item => !validate.validators.containsI(item.toLowerCase(), options, key) - ) - ) { + if (value.some(item => !validate.validators.containsI(item.toLowerCase(), options, key))) { return undefined; } - return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue - )}`; + return `No item of ${value.join(',')} contains ${JSON.stringify(testValue)}`; } if (Array.isArray(testValue)) { if ( - testValue.some( - subTest => !validate.validators.containsI(value, subTest, key) - ) + testValue.some(subTest => !validate.validators.containsI(value, subTest.toLowerCase(), key)) ) { return; } return `${key} must contain at least one of ${testValue.join(',')}`; } - if ( - testValue && - value.indexOf && - value.toLowerCase().indexOf(testValue) === -1 - ) { + if (testValue && value.indexOf && value.toLowerCase().indexOf(testValue.toLowerCase()) === -1) { return key + 'must contain any case of' + testValue; } }; - -validate.validators.contains = function(value, options, key) { - const testValue = options?.value ?? options; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['cat', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.contains = function (value, options, key) { + const testValue = getTestValue(options); if (Array.isArray(value)) { if (value.some(item => !validate.validators.contains(item, options, key))) { return undefined; } - return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue - )}`; + return `No item of ${value.join(',')} contains ${JSON.stringify(testValue)}`; } if (Array.isArray(testValue)) { - if ( - testValue.some( - subTest => !validate.validators.contains(value, subTest, key) - ) - ) { + if (testValue.some(subTest => !validate.validators.contains(value, subTest, key))) { return; } return `${key} must contain at least one of ${testValue.join(',')}`; @@ -73,50 +227,237 @@ validate.validators.contains = function(value, options, key) { return key + 'must contain ' + testValue; } }; - -validate.validators.doesNotContain = function(value, options, key) { - if (options && value.indexOf && value.indexOf(options.value) !== -1) { - return key + 'cannot contain ' + options.value; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Fail) + * = 'dog' (Valid) + * = ['cat', 'de'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ +validate.validators.doesNotContain = function (value, options, key) { + const containsResult = validate.validators.contains(value, options, key); + if (!containsResult) { + return `No item of ${value} should contain ${getTestValue(options)}`; } }; -validate.validators.startsWith = function(value, options, key) { - if (options && value.startsWith && !value.startsWith(options.value)) { - return key + 'must start with ' + options.value; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'DEF' (Fail) + * = 'dog' (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ +validate.validators.doesNotContainI = function (value, options, key) { + const containsResult = validate.validators.containsI(value, options, key); + if (!containsResult) { + return `No item of ${value} should not contain ${getTestValue(options)}`; } }; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘Att’ (Fail) + * = ['cat', 'dog', 'Att'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'GH'] (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.startsWith = function (value, options, key) { + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } -validate.validators.endsWith = function(value, options, key) { - if (options && value.endsWith && !value.endsWith(options.value)) { - return key + 'must end with ' + options.value; + if (typeof value === 'string') { + if (!testValues.some(testValue => value.startsWith(testValue))) { + return key + ' must start with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].startsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } + + if (!valid) { + return key + ' must start with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return 'Value must be a string or an array'; } }; -const getTestValue = options => options?.value ?? options; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘TED’ (Fail) + * = ‘ted’ (Valid) + * = ['cat', 'dog', 'ted'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'HI'] (Valid) + * = ['cat', 'hi'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.endsWith = function (value, options, key) { + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } + + if (typeof value === 'string') { + if (!testValues.some(testValue => value.endsWith(testValue))) { + return key + ' must end with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].endsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } -validate.validators.greaterThan = function(value, options, key) { + if (!valid) { + return key + ' must end with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return key + ' must be a string or an array'; + } +}; +/** + * @example + * value = 30 + * testValue = 20 (Valid) + * = 40 (Fail) + * + * */ +validate.validators.greaterThan = function (value, options, key) { + const testValue = getTestValue(options); + if (Array.isArray(value) || typeof value === 'string') { + return `${key} is not allowed as an array or string`; + } + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value >= testValue[0])) { + return `${key} must be greater than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value >= testValue)) { + return key + ' must be greater than ' + testValue; + } + } +}; +/** + * @example + * value = 30 + * testValue = 40 (Valid) + * = 20 (Fail) + * + * */ +validate.validators.lessThan = function (value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value === null || value <= testValue) { - return key + 'with value ' + value + ' must be greater than ' + testValue; + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value <= testValue[0])) { + return `${key} must be less than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value <= testValue)) { + return key + ' must be less than ' + testValue; + } } }; +/** + * @example -validate.validators.range = function(value, options, key) { + * + * value = 50 + * testValue = [10,60] (Valid) + * = [60, 10] (Valid) + * = [0, 10] (Fail) + * = [70, 80] (Fail) + * = 45 (Fail) + * = [45] (Fail) + * + * */ +validate.validators.range = function (value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value < testValue[0] || value > testValue[1]) { - return ( - key + - 'with value ' + - value + - ' must be between ' + - testValue[0] + - ' and ' + - testValue[1] - ); + if (Array.isArray(testValue) && testValue.length === 2) { + const min = Math.min(testValue[0], testValue[1]); + const max = Math.max(testValue[0], testValue[1]); + if (value === undefined || value < min || value > max) { + return `${key} with value ${value} must be between ${min} and ${max}`; + } + } else { + return `${key} must be an array of length 2`; } }; validate.validators.notNull = value => value === null || value === undefined ? 'Value is null' : undefined; - +const getTestValue = options => { + if (Array.isArray(options)) { + return options.map(option => option?.value ?? option); + } else { + return options?.value ?? options; + } +}; +const dicomTagToArray = value => { + let dicomArrayValue; + if (!Array.isArray(value)) { + dicomArrayValue = [value]; + } else { + dicomArrayValue = [...value]; + } + return dicomArrayValue; +}; export default validate; diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.test.js b/platform/core/src/services/HangingProtocolService/lib/validator.test.js index 67b7ac52e58..4fa200177dd 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.test.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.test.js @@ -2,138 +2,357 @@ import validate from './validator.js'; describe('validator', () => { const attributeMap = { - str: 'string', + str: 'Attenuation Corrected', upper: 'UPPER', num: 3, nullValue: null, list: ['abc', 'def', 'GHI'], + listStr: ['Attenuation Corrected'], }; const options = { format: 'grouped', }; - describe('contains', () => { - it('returns match any list contains', () => { + describe('equals', () => { + it('returned undefined on strictly equals', () => { + expect( + validate(attributeMap, { listStr: { equals: ['Attenuation'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { equals: 'Attenuation' } }, [options]) + ).not.toBeUndefined(); expect( - validate(attributeMap, { list: { contains: 'a' } }, [options]) + validate(attributeMap, { listStr: { equals: 'Attenuation Corrected' } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { str: { contains: 'i' } }, [options]) + validate(attributeMap, { listStr: { equals: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { str: { contains: ['i'] } }, [options]) + validate(attributeMap, { str: { equals: 'Attenuation Corrected' } }, [options]) + ).toBeUndefined(); + + expect( + validate(attributeMap, { str: { equals: { value: 'Attenuation Corrected' } } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['a'] } }, [options]) + validate(attributeMap, { str: { equals: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['z', 'd'] } }, [options]) + validate(attributeMap, { str: { equals: ['Attenuation'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: ['abc', 'def', 'GHI'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['z'] } }, [options]) + validate(attributeMap, { list: { equals: ['abc', 'GHI', 'def'] } }, [options]) ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: { value: ['abc', 'def', 'GHI'] } } }, [options]) + ).toBeUndefined(); }); }); - - describe('containsI', () => { - it('returns match any list contains case insensitive', () => { + describe('doesNotEqual', () => { + it('returns undefined if value does not equal ', () => { expect( - validate(attributeMap, { upper: { containsI: ['bye', 'pre'] } }, [ - options, - ]) + validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation Corrected' } }, [options]) ).not.toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: 'hi' } }, [options]) + validate(attributeMap, { listStr: { doesNotEqual: ['Attenuation Corrected'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation' } }, [options]) ).toBeUndefined(); + expect( - validate(attributeMap, { list: { containsI: ['hi', 'bye'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: 'Attenuation' } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: { value: 'Attenuation' } } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) + validate(attributeMap, { str: { doesNotEqual: ['Attenuation'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: ['abc', 'def'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotEqual: ['abc', 'GHI', 'def'] } }, [options]) ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotEqual: ['abc', 'def', 'GHI'] } }, [options]) + ).not.toBeUndefined(); }); }); - - describe('equals', () => { - it('returned undefined on equals', () => { + describe('includes', () => { + it('returns match any list includes', () => { + expect( + validate(attributeMap, { listStr: { includes: 'Attenuation Corrected' } }, [options]) + ).not.toBeUndefined(); expect( - validate(attributeMap, { str: { equals: attributeMap.str } }, [options]) + validate(attributeMap, { listStr: { includes: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate( - attributeMap, - { num: { equals: { value: attributeMap.num } } }, - [options] - ) + validate(attributeMap, { listStr: { includes: ['Attenuation Corrected', 'Corrected'] } }, [ + options, + ]) ).toBeUndefined(); - }); - - it('returns error on not equals', () => { expect( - validate(attributeMap, { str: { equals: 'abc' } }, [options]) + validate(attributeMap, { listStr: { includes: ['Attenuation', 'Corrected'] } }, [options]) ).not.toBeUndefined(); expect( - validate( - attributeMap, - { num: { equals: { value: 1 + attributeMap.num } } }, - [options] - ) + validate(attributeMap, { str: { includes: ['Attenuation Corrected', 'Corrected'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { includes: ['Attenuation', 'Corrected'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { includes: ['abc'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['GHI', 'HI'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['HI', 'bye'] } }, [options]) ).not.toBeUndefined(); }); }); - - describe('greaterThan', () => { - it('returns undefined on greaterThan', () => { + describe('doesNotInclude', () => { + it('returns undefined if list does not includes', () => { + expect( + validate(attributeMap, { listStr: { doesNotInclude: 'Attenuation Corrected' } }, [options]) + ).not.toBeUndefined(); expect( validate( attributeMap, - { num: { greaterThan: { value: attributeMap.num - 1 } } }, + { + listStr: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] }, + }, [options] ) - ).toBeUndefined(); + ).not.toBeUndefined(); expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [ + validate(attributeMap, { listStr: { doesNotInclude: ['Attenuation', 'Corrected'] } }, [ options, ]) ).toBeUndefined(); - }); - - it('returns error on not greater than', () => { expect( validate( attributeMap, - { num: { greaterThan: { value: attributeMap.num } } }, + { str: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] } }, [options] ) ).not.toBeUndefined(); expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num } }, [ + validate(attributeMap, { str: { doesNotInclude: ['Attenuation', 'Corrected'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: ['Corr'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: 'abc' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['abc'] } } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['att', 'cor'] } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['abc', 'def', 'dog'] } } }, [ options, ]) ).not.toBeUndefined(); }); + }); + describe('containsI', () => { + it('returns match any list contains case insensitive', () => { + expect( + validate(attributeMap, { upper: { containsI: ['hi', 'pre'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { containsI: 'hi' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ghi', 'bye'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [options]) + ).toBeUndefined(); + }); + }); + describe('contains', () => { + it('returns match any list contains', () => { + expect(validate(attributeMap, { str: { contains: 'Corr' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: { value: 'Corr' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { contains: ['Corr'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['corr'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['Att', 'Wall'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: 'GH' } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: ['ab'] } }, [options])).toBeUndefined(); + + expect( + validate(attributeMap, { list: { contains: ['z', 'bc'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: ['z'] } }, [options])).not.toBeUndefined(); + }); + }); - it('returns error on undefined value', () => { + describe('doesNotContain', () => { + it('returns undefined if string does not contain specified value', () => { expect( - validate( - attributeMap, - { numUndefined: { greaterThan: { value: 3 } } }, - [options] - ) + validate(attributeMap, { str: { doesNotContain: ['att', 'wall'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'corr' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: { value: 'corr' } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['att', 'cor'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['Att', 'cor'] } }, [options]) ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['GHI', 'hi'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['hi'] } }, [options]) + ).toBeUndefined(); + }); + }); + describe('doesNotContainI', () => { + it('returns undefined if string does not contain specified value', () => { + expect( + validate(attributeMap, { str: { doesNotContainI: 'corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['att', 'cor'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['Att', 'wall'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: 'bye' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('startsWith', () => { + it('returns undefined if string starts with specified value', () => { + expect( + validate(attributeMap, { str: { startsWith: { value: 'Atte' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { startsWith: 'Att' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog', 'Att'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { startsWith: ['GH'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['de', 'bye'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['hi', 'bye'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('endsWith', () => { + it('returns undefined if string ends with specified value', () => { + expect(validate(attributeMap, { str: { endsWith: 'ted' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: { value: 'ted' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { endsWith: ['ted'] } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { str: { endsWith: ['Att'] } }, [options])).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: ['cat', 'dog', 'ted'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { endsWith: ['HI'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bc', 'dog', 'ted'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bye', 'dog'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + + describe('greaterThan', () => { + it('returns undefined on greaterThan', () => { + expect( + validate(attributeMap, { num: { greaterThan: { value: attributeMap.num - 1 } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: [attributeMap.num - 1] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: [attributeMap.num + 1] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('lessThan', () => { + it('returns undefined on lessThan', () => { + expect( + validate(attributeMap, { num: { lessThan: { value: attributeMap.num + 1 } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: attributeMap.num + 1 } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num + 1] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num - 1] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('range', () => { + it('returns undefined if the value is between', () => { + expect( + validate(attributeMap, { num: { range: [attributeMap.num + 1, attributeMap.num - 1] } }, [ + options, + ]) + ).toBeUndefined(); + expect(validate(attributeMap, { num: { range: [1, 4] } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { num: { range: [1, 2] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: [4, 5] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: [5] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: 5 } }, [options])).not.toBeUndefined(); }); }); }); diff --git a/platform/core/src/services/MeasurementService/MeasurementService.test.js b/platform/core/src/services/MeasurementService/MeasurementService.test.js index 78c13c83703..93517d58c2f 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.test.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.test.js @@ -126,13 +126,7 @@ describe('MeasurementService.js', () => { it('throws Error if no matching criteria provided', () => { expect(() => { - measurementService.addMapping( - source, - annotationType, - null, - toSourceSchema, - toMeasurement - ); + measurementService.addMapping(source, annotationType, null, toSourceSchema, toMeasurement); }).toThrow(new Error('Matching criteria not provided.')); }); @@ -194,34 +188,16 @@ describe('MeasurementService.js', () => { toSourceSchema, toMeasurement ); - const measurementId = source.annotationToMeasurement( - annotationType, - annotation - ); - const mappedAnnotation = source.getAnnotation( - annotationType, - measurementId - ); + const measurementId = source.annotationToMeasurement(annotationType, annotation); + const mappedAnnotation = source.getAnnotation(annotationType, measurementId); expect(annotation).toBe(mappedAnnotation); }); it('get annotation based on source and annotationType', () => { - measurementService.addMapping( - source, - annotationType, - {}, - toSourceSchema, - toMeasurement - ); - const measurementId = source.annotationToMeasurement( - annotationType, - annotation - ); - const mappedAnnotation = source.getAnnotation( - annotationType, - measurementId - ); + measurementService.addMapping(source, annotationType, {}, toSourceSchema, toMeasurement); + const measurementId = source.annotationToMeasurement(annotationType, annotation); + const mappedAnnotation = source.getAnnotation(annotationType, measurementId); expect(annotation).toBe(mappedAnnotation); }); @@ -323,9 +299,7 @@ describe('MeasurementService.js', () => { /* Add new measurement */ source.annotationToMeasurement(annotationType, newMeasurement); - const savedMeasurement = measurementService.getMeasurement( - newMeasurement.uid - ); + const savedMeasurement = measurementService.getMeasurement(newMeasurement.uid); /* Clear dynamic data */ delete newMeasurement.modifiedTimestamp; @@ -384,10 +358,7 @@ describe('MeasurementService.js', () => { let addCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_ADDED, - () => (addCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_ADDED, () => (addCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ const uid = source.annotationToMeasurement(annotationType, measurement); @@ -409,20 +380,13 @@ describe('MeasurementService.js', () => { let updateCallbackWasCalled = false; /* Subscribe to update event */ - measurementService.subscribe( - MEASUREMENT_UPDATED, - () => (updateCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_UPDATED, () => (updateCallbackWasCalled = true)); /* Create measurement */ const uid = source.annotationToMeasurement(annotationType, measurement); /* Update measurement */ - source.annotationToMeasurement( - annotationType, - { uid, ...measurement }, - true - ); + source.annotationToMeasurement(annotationType, { uid, ...measurement }, true); expect(updateCallbackWasCalled).toBe(true); }); @@ -468,10 +432,7 @@ describe('MeasurementService.js', () => { let addCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_ADDED, - () => (addCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_ADDED, () => (addCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ // expect exceptions for unmapped measurements @@ -502,10 +463,7 @@ describe('MeasurementService.js', () => { let removeCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_REMOVED, - () => (removeCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_REMOVED, () => (removeCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ // expect exceptions for unmapped measurements diff --git a/platform/core/src/services/MeasurementService/MeasurementService.ts b/platform/core/src/services/MeasurementService/MeasurementService.ts index bd1ac8b8436..dde9ff4d217 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.ts +++ b/platform/core/src/services/MeasurementService/MeasurementService.ts @@ -146,9 +146,7 @@ class MeasurementService extends PubSubService { // check if valuetype is valid , and if values are strings if (!valueType || typeof valueType !== 'object') { - console.warn( - `MeasurementService: addValueType: invalid valueType: ${valueType}` - ); + console.warn(`MeasurementService: addValueType: invalid valueType: ${valueType}`); return; } @@ -178,10 +176,7 @@ class MeasurementService extends PubSubService { return this.measurements.get(measurementUID); } - public setMeasurementSelected( - measurementUID: string, - selected: boolean - ): void { + public setMeasurementSelected(measurementUID: string, selected: boolean): void { const measurement = this.getMeasurement(measurementUID); if (!measurement) { return; @@ -230,17 +225,8 @@ class MeasurementService extends PubSubService { version, }; - source.annotationToMeasurement = ( - annotationType, - annotation, - isUpdate = false - ) => { - return this.annotationToMeasurement( - source, - annotationType, - annotation, - isUpdate - ); + source.annotationToMeasurement = (annotationType, annotation, isUpdate = false) => { + return this.annotationToMeasurement(source, annotationType, annotation, isUpdate); }; source.remove = (measurementUID, eventDetails) => { @@ -281,13 +267,7 @@ class MeasurementService extends PubSubService { * @param {Function} toMeasurementSchema Mapping function to measurement schema * @return void */ - addMapping( - source, - annotationType, - matchingCriteria, - toAnnotationSchema, - toMeasurementSchema - ) { + addMapping(source, annotationType, matchingCriteria, toAnnotationSchema, toMeasurementSchema) { if (!this._isValidSource(source)) { throw new Error('Invalid source.'); } @@ -321,11 +301,7 @@ class MeasurementService extends PubSubService { this.mappings[source.uid] = [mapping]; } - log.info( - `New measurement mapping added to source '${this._getSourceToString( - source - )}'.` - ); + log.info(`New measurement mapping added to source '${this._getSourceToString(source)}'.`); } /** @@ -348,20 +324,13 @@ class MeasurementService extends PubSubService { } const measurement = this.getMeasurement(measurementUID); - const mapping = this._getMappingByMeasurementSource( - measurement, - annotationType - ); + const mapping = this._getMappingByMeasurementSource(measurement, annotationType); if (mapping) { return mapping.toAnnotationSchema(measurement, annotationType); } - const matchingMapping = this._getMatchingMapping( - source, - annotationType, - measurement - ); + const matchingMapping = this._getMatchingMapping(source, annotationType, measurement); if (matchingMapping) { log.info('Matching mapping found:', matchingMapping); @@ -380,10 +349,7 @@ class MeasurementService extends PubSubService { modifiedTimestamp: Math.floor(Date.now() / 1000), }; - log.info( - `Updating internal measurement representation...`, - updatedMeasurement - ); + log.info(`Updating internal measurement representation...`, updatedMeasurement); this.measurements.set(measurementUID, updatedMeasurement); @@ -405,13 +371,7 @@ class MeasurementService extends PubSubService { * @param {object} data The data you wish to add to the source. * @param {function} toMeasurementSchema A function to get the `data` into the same shape as the source annotationType. */ - addRawMeasurement( - source, - annotationType, - data, - toMeasurementSchema, - dataSource = {} - ) { + addRawMeasurement(source, annotationType, data, toMeasurementSchema, dataSource = {}) { if (!this._isValidSource(source)) { log.warn('Invalid source. Exiting early.'); return; @@ -425,9 +385,7 @@ class MeasurementService extends PubSubService { } if (!this._sourceHasMappings(source)) { - log.warn( - `No measurement mappings found for '${sourceInfo}' source. Exiting early.` - ); + log.warn(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`); return; } @@ -484,7 +442,7 @@ class MeasurementService extends PubSubService { }); } - return newMeasurement.id; + return newMeasurement.uid; } /** @@ -496,12 +454,7 @@ class MeasurementService extends PubSubService { * @param {boolean} isUpdate is this an update or an add/completed instead? * @return {string} A measurement uid */ - annotationToMeasurement( - source, - annotationType, - sourceAnnotationDetail, - isUpdate = false - ) { + annotationToMeasurement(source, annotationType, sourceAnnotationDetail, isUpdate = false) { if (!this._isValidSource(source)) { throw new Error('Invalid source.'); } @@ -512,9 +465,7 @@ class MeasurementService extends PubSubService { const sourceInfo = this._getSourceToString(source); if (!this._sourceHasMappings(source)) { - throw new Error( - `No measurement mappings found for '${sourceInfo}' source. Exiting early.` - ); + throw new Error(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`); } let measurement = {}; @@ -566,7 +517,7 @@ class MeasurementService extends PubSubService { }; if (oldMeasurement) { - // TODO: Ultimately, each annotation should have a selected flag right from the soure. + // TODO: Ultimately, each annotation should have a selected flag right from the source. // For now, it is just added in OHIF here and in setMeasurementSelected. this.measurements.set(internalUID, newMeasurement); if (isUpdate) { @@ -599,8 +550,7 @@ class MeasurementService extends PubSubService { remove(measurementUID, source, eventDetails) { if ( !measurementUID || - (!this.measurements.has(measurementUID) && - !this.unmappedMeasurements.has(measurementUID)) + (!this.measurements.has(measurementUID) && !this.unmappedMeasurements.has(measurementUID)) ) { log.warn(`No uid provided, or unable to find measurement by uid.`); return; @@ -649,10 +599,7 @@ class MeasurementService extends PubSubService { * merely causes it to fire the event with the isConsumed set to true. */ - public jumpToMeasurement( - viewportIndex: number, - measurementUID: string - ): void { + public jumpToMeasurement(viewportId: string, measurementUID: string): void { const measurement = this.measurements.get(measurementUID); if (!measurement) { @@ -660,7 +607,7 @@ class MeasurementService extends PubSubService { return; } const consumableEvent = this.createConsumableEvent({ - viewportIndex, + viewportId, measurement, }); @@ -682,9 +629,7 @@ class MeasurementService extends PubSubService { _getMappingByMeasurementSource(measurement, annotationType) { if (this._isValidSource(measurement.source)) { - return this.mappings[measurement.source.uid].find( - m => m.annotationType === annotationType - ); + return this.mappings[measurement.source.uid].find(m => m.annotationType === annotationType); } } @@ -705,10 +650,7 @@ class MeasurementService extends PubSubService { /* Criteria Matching */ return sourceMappingsByDefinition.find(({ matchingCriteria }) => { - return ( - measurement.points && - measurement.points.length === matchingCriteria.points - ); + return measurement.points && measurement.points.length === matchingCriteria.points; }); } @@ -739,10 +681,7 @@ class MeasurementService extends PubSubService { * @return {boolean} Validation if source has mappings */ _sourceHasMappings(source) { - return ( - Array.isArray(this.mappings[source.uid]) && - this.mappings[source.uid].length - ); + return Array.isArray(this.mappings[source.uid]) && this.mappings[source.uid].length; } /** diff --git a/platform/core/src/services/ServicesManager.test.js b/platform/core/src/services/ServicesManager.test.js index 30b115975b8..1a4337034eb 100644 --- a/platform/core/src/services/ServicesManager.test.js +++ b/platform/core/src/services/ServicesManager.test.js @@ -38,9 +38,7 @@ describe('ServicesManager', () => { [{ name: 'UIModalTestService', create: jest.fn() }, fakeConfiguration], ]); - expect(servicesManager.registerService.mock.calls[1][1]).toEqual( - fakeConfiguration - ); + expect(servicesManager.registerService.mock.calls[1][1]).toEqual(fakeConfiguration); }); }); @@ -78,9 +76,7 @@ describe('ServicesManager', () => { it('tracks which services have been registered', () => { servicesManager.registerService(fakeService); - expect(servicesManager.registeredServiceNames).toContain( - fakeService.name - ); + expect(servicesManager.registeredServiceNames).toContain(fakeService.name); }); it('logs a warning if the service has an name that has already been registered', () => { @@ -95,9 +91,7 @@ describe('ServicesManager', () => { servicesManager.registerService(fakeService, configuration); - expect(fakeService.create.mock.calls[0][0].configuration.config).toBe( - configuration.config - ); + expect(fakeService.create.mock.calls[0][0].configuration.config).toBe(configuration.config); }); }); }); diff --git a/platform/core/src/services/ServicesManager.ts b/platform/core/src/services/ServicesManager.ts index 3d2843dbf9b..445e5aec397 100644 --- a/platform/core/src/services/ServicesManager.ts +++ b/platform/core/src/services/ServicesManager.ts @@ -19,9 +19,7 @@ export default class ServicesManager { */ registerService(service, configuration = {}) { if (!service) { - log.warn( - 'Attempting to register a null/undefined service. Exiting early.' - ); + log.warn('Attempting to register a null/undefined service. Exiting early.'); return; } diff --git a/platform/core/src/services/StateSyncService/StateSyncService.ts b/platform/core/src/services/StateSyncService/StateSyncService.ts index 5a28bc04165..ef0d6a3624a 100644 --- a/platform/core/src/services/StateSyncService/StateSyncService.ts +++ b/platform/core/src/services/StateSyncService/StateSyncService.ts @@ -6,7 +6,7 @@ const EVENTS = {}; type Obj = Record; type StateConfig = { - /** clearOnModeExit defines state configuraion that is cleared automatically on + /** clearOnModeExit defines state configuration that is cleared automatically on * exiting a mode. This clearing occurs after the mode onModeExit, * so it is possible to preserve desired state during exit to be restored * later. @@ -40,7 +40,7 @@ export default class StateSyncService extends PubSubService { this.configuration = configuration || {}; } - public init(extensionManager: ExtensionManager): void { } + public init(extensionManager: ExtensionManager): void {} /** Registers a new sync store called `id`. The state * defines how the state is stored, and any default clearing of the diff --git a/platform/core/src/services/ToolBarService/ToolbarService.ts b/platform/core/src/services/ToolBarService/ToolbarService.ts index 4a3cf2429e9..a39d3eca58b 100644 --- a/platform/core/src/services/ToolBarService/ToolbarService.ts +++ b/platform/core/src/services/ToolBarService/ToolbarService.ts @@ -2,12 +2,37 @@ import merge from 'lodash.merge'; import { CommandsManager } from '../../classes'; import { ExtensionManager } from '../../extensions'; import { PubSubService } from '../_shared/pubSubServiceInterface'; +import type { RunCommand, Commands } from '../../types/Command'; const EVENTS = { TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified', TOOL_BAR_STATE_MODIFIED: 'event::toolBarService:toolBarStateModified', }; +export type ButtonListeners = Record; + +export interface ButtonProps { + primary?: Button; + secondary?: Button; + items?: Button[]; +} + +export interface Button extends Commands { + id: string; + icon?: string; + label?: string; + type?: string; + tooltip?: string; + isActive?: boolean; + listeners?: ButtonListeners; + props?: ButtonProps; +} + +export interface ExtraButtonOptions { + listeners?: ButtonListeners; + isActive?: boolean; +} + export default class ToolbarService extends PubSubService { public static REGISTRATION = { name: 'toolbarService', @@ -18,7 +43,27 @@ export default class ToolbarService extends PubSubService { }, }; - buttons: Record = {}; + public static _createButton( + type: string, + id: string, + icon: string, + label: string, + commands: Command | Commands, + tooltip?: string, + extraOptions?: ExtraButtonOptions + ): Button { + return { + id, + icon, + label, + type, + commands, + tooltip, + ...extraOptions, + }; + } + + buttons: Record = {}; state: { primaryToolId: string; toggles: Record; @@ -54,7 +99,7 @@ export default class ToolbarService extends PubSubService { this.buttons = {}; } - onModeEnter() { + public onModeEnter(): void { this.reset(); } @@ -65,10 +110,18 @@ export default class ToolbarService extends PubSubService { * used for calling the specified interaction. That is, the command is * called with {...commandOptions,...options} */ - recordInteraction(interaction, options?: Record) { - if (!interaction) return; + public recordInteraction(interaction, options?: Record) { + if (!interaction) { + return; + } const commandsManager = this._commandsManager; - const { groupId, itemId, interactionType, commands } = interaction; + const { groupId, itemId, commands, type } = interaction; + let { interactionType } = interaction; + + // if not interaction type, assume the type can be used + if (!interactionType) { + interactionType = type; + } switch (interactionType) { case 'action': { @@ -88,14 +141,14 @@ export default class ToolbarService extends PubSubService { } case 'tool': { try { - commands.forEach( - ({ commandName = 'setToolActive', commandOptions, context }) => { - commandsManager.runCommand(commandName, commandOptions, context); - } - ); - - // only set the primary tool if no error was thrown - this.state.primaryToolId = itemId; + commands.forEach(({ commandName = 'setToolActive', commandOptions, context }) => { + commandsManager.runCommand(commandName, commandOptions, context); + }); + + // only set the primary tool if no error was thrown. + // if the itemId is not undefined use it; otherwise, set the first tool in + // the commands as the primary tool + this.state.primaryToolId = itemId || commands[0].commandOptions?.toolName; } catch (error) { console.warn(error); } @@ -108,9 +161,7 @@ export default class ToolbarService extends PubSubService { // only toggle if a command was executed this.state.toggles[itemId] = - this.state.toggles[itemId] === undefined - ? true - : !this.state.toggles[itemId]; + this.state.toggles[itemId] === undefined ? true : !this.state.toggles[itemId]; if (!commands) { break; @@ -154,7 +205,7 @@ export default class ToolbarService extends PubSubService { // unsubscribe = commandsManager.runCommand(commandName, commandOptions); // } - // // Storing the unsubscribe for later reseting + // // Storing the unsubscribe for later resetting // if (unsubscribe && typeof unsubscribe === 'function') { // if (this.unsubscriptions.indexOf(unsubscribe) === -1) { // this.unsubscriptions.push(unsubscribe); @@ -166,7 +217,7 @@ export default class ToolbarService extends PubSubService { this.state.groups[groupId] = itemId; } - this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, {}); + this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, { ...this.state }); } getButtons() { @@ -174,12 +225,22 @@ export default class ToolbarService extends PubSubService { } getActiveTools() { - return [this.state.primaryToolId, ...Object.keys(this.state.toggles)]; + const activeTools = [this.state.primaryToolId]; + Object.keys(this.state.toggles).forEach(key => { + if (this.state.toggles[key]) { + activeTools.push(key); + } + }); + return activeTools; } - /** Sets the toggle state of a button to the isActive state */ - public setActive(id: string, isActive: boolean): void { - if (isActive) { + getActivePrimaryTool() { + return this.state.primaryToolId; + } + + /** Sets the toggle state of a button to the isToggled state */ + public setToggled(id: string, isToggled: boolean): void { + if (isToggled) { this.state.toggles[id] = true; } else { delete this.state.toggles[id]; @@ -197,10 +258,27 @@ export default class ToolbarService extends PubSubService { } } - getButton(id) { + public getButton(id: string): Button { return this.buttons[id]; } + /** Gets a nested button, found in the items/props for the children */ + public getNestedButton(id: string): Button { + if (this.buttons[id]) { + return this.buttons[id]; + } + for (const buttonId of Object.keys(this.buttons)) { + const { primary, items } = this.buttons[buttonId].props || {}; + if (primary?.id === id) { + return primary; + } + const found = items?.find(childButton => childButton.id === id); + if (found) { + return found; + } + } + } + setButtons(buttons) { this.buttons = buttons; this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, { @@ -211,14 +289,9 @@ export default class ToolbarService extends PubSubService { _buttonTypes() { const buttonTypes = {}; - const registeredToolbarModules = this.extensionManager.modules[ - 'toolbarModule' - ]; - - if ( - Array.isArray(registeredToolbarModules) && - registeredToolbarModules.length - ) { + const registeredToolbarModules = this.extensionManager.modules['toolbarModule']; + + if (Array.isArray(registeredToolbarModules) && registeredToolbarModules.length) { registeredToolbarModules.forEach(toolbarModule => toolbarModule.module.forEach(def => { buttonTypes[def.name] = def; @@ -273,10 +346,24 @@ export default class ToolbarService extends PubSubService { this.buttons[button.id] = button; } }); + this._setTogglesForButtonItems(buttons); this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {}); } + _setTogglesForButtonItems(buttons) { + if (!buttons) { + return; + } + + buttons.forEach(buttonItem => { + if (buttonItem.type === 'toggle') { + this.setToggled(buttonItem.id, buttonItem.isActive); + } + this._setTogglesForButtonItems(buttonItem.props?.items); + }); + } + /** * * @param {*} btn @@ -285,6 +372,10 @@ export default class ToolbarService extends PubSubService { * @param {*} props - Props set by the Viewer layer */ _mapButtonToDisplay(btn, btnSection, metadata, props) { + if (!btn) { + return; + } + const { id, type, component } = btn; const buttonType = this._buttonTypes()[type]; @@ -300,8 +391,6 @@ export default class ToolbarService extends PubSubService { } getButtonComponentForUIType(uiType: string) { - return uiType - ? this._buttonTypes()[uiType]?.defaultComponent ?? null - : null; + return uiType ? this._buttonTypes()[uiType]?.defaultComponent ?? null : null; } } diff --git a/platform/core/src/services/UIModalService/index.ts b/platform/core/src/services/UIModalService/index.ts index 3b33d380689..865a2f4b466 100644 --- a/platform/core/src/services/UIModalService/index.ts +++ b/platform/core/src/services/UIModalService/index.ts @@ -71,10 +71,7 @@ class UIModalService { * show: showImplementation, * } */ - setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, - }) { + setServiceImplementation({ hide: hideImplementation, show: showImplementation }) { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UINotificationService/index.ts b/platform/core/src/services/UINotificationService/index.ts index 78a2882afd8..b49f46fa7df 100644 --- a/platform/core/src/services/UINotificationService/index.ts +++ b/platform/core/src/services/UINotificationService/index.ts @@ -38,10 +38,7 @@ class UINotificationService { * show: showImplementation, * } */ - public setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, - }): void { + public setServiceImplementation({ hide: hideImplementation, show: showImplementation }): void { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js index 770fccc05fa..5c5053648fe 100644 --- a/platform/core/src/services/UIViewportDialogService/index.js +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -4,7 +4,7 @@ * @typedef {Object} ViewportDialogProps * @property {ReactElement|HTMLElement} [content=null] Modal content. * @property {Object} [contentProps=null] Modal content props. - * @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key. + * @property {boolean} [viewportId=false] Modal is dismissible via the esc key. */ const name = 'uiViewportDialogService'; @@ -22,21 +22,13 @@ const serviceImplementation = { }; /** - * Show a new UI viewport dialog on the specified viewportIndex; + * Show a new UI viewport dialog on the specified viewportId; * - * @param {ViewportDialogProps} props { content, contentProps, viewportIndex } + * @param {ViewportDialogProps} props { content, contentProps, viewportId } */ -function _show({ - viewportIndex, - id, - type, - message, - actions, - onSubmit, - onOutsideClick, -}) { +function _show({ viewportId, id, type, message, actions, onSubmit, onOutsideClick }) { return serviceImplementation._show({ - viewportIndex, + viewportId, id, type, message, @@ -59,13 +51,10 @@ function _hide() { * @param {*} { * hide: hideImplementation, * show: showImplementation, - * viewportIndex, + * viewportId, * } */ -function setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, -}) { +function setServiceImplementation({ hide: hideImplementation, show: showImplementation }) { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js b/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js index e112542e8a0..5c1ec921b60 100644 --- a/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js +++ b/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js @@ -18,8 +18,7 @@ const serviceImplementation = { _getUser: () => console.warn('_setUser() NOT IMPLEMENTED'), _getAuthorizationHeader: () => {}, // TODO: have enabled/disabled state? //console.warn('_getAuthorizationHeader() NOT IMPLEMENTED'), - _handleUnauthenticated: () => - console.warn('_handleUnauthenticated() NOT IMPLEMENTED'), + _handleUnauthenticated: () => console.warn('_handleUnauthenticated() NOT IMPLEMENTED'), _reset: () => console.warn('reset() NOT IMPLEMENTED'), _set: () => console.warn('set() NOT IMPLEMENTED'), }; diff --git a/platform/core/src/services/ViewportGridService/ViewportGridService.ts b/platform/core/src/services/ViewportGridService/ViewportGridService.ts index a5fca6e6a97..ee430ccacbe 100644 --- a/platform/core/src/services/ViewportGridService/ViewportGridService.ts +++ b/platform/core/src/services/ViewportGridService/ViewportGridService.ts @@ -3,7 +3,7 @@ import { getPresentationIds, PresentationIds } from './getPresentationIds'; class ViewportGridService extends PubSubService { public static readonly EVENTS = { - ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged', + ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged', LAYOUT_CHANGED: 'event::layoutChanged', GRID_STATE_CHANGED: 'event::gridStateChanged', }; @@ -27,7 +27,7 @@ class ViewportGridService extends PubSubService { public setServiceImplementation({ getState: getStateImplementation, - setActiveViewportIndex: setActiveViewportIndexImplementation, + setActiveViewportId: setActiveViewportIdImplementation, setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation, setLayout: setLayoutImplementation, reset: resetImplementation, @@ -38,9 +38,8 @@ class ViewportGridService extends PubSubService { if (getStateImplementation) { this.serviceImplementation._getState = getStateImplementation; } - if (setActiveViewportIndexImplementation) { - this.serviceImplementation._setActiveViewportIndex = - setActiveViewportIndexImplementation; + if (setActiveViewportIdImplementation) { + this.serviceImplementation._setActiveViewport = setActiveViewportIdImplementation; } if (setDisplaySetsForViewportsImplementation) { this.serviceImplementation._setDisplaySetsForViewports = @@ -59,18 +58,15 @@ class ViewportGridService extends PubSubService { this.serviceImplementation._set = setImplementation; } if (getNumViewportPanesImplementation) { - this.serviceImplementation._getNumViewportPanes = - getNumViewportPanesImplementation; + this.serviceImplementation._getNumViewportPanes = getNumViewportPanesImplementation; } } - public setActiveViewportIndex(index) { - this.serviceImplementation._setActiveViewportIndex(index); + public setActiveViewportId(id: string) { + this.serviceImplementation._setActiveViewport(id); const state = this.getState(); - const viewportId = state.viewports[index]?.viewportOptions?.viewportId; - this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, { - viewportIndex: index, - viewportId, + this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, { + viewportId: id, }); } @@ -78,6 +74,11 @@ class ViewportGridService extends PubSubService { return this.serviceImplementation._getState(); } + public getActiveViewportId() { + const state = this.getState(); + return state.activeViewportId; + } + public setDisplaySetsForViewport(props) { // Just update a single viewport, but use the multi-viewport update for it. this.setDisplaySetsForViewports([props]); @@ -89,14 +90,11 @@ class ViewportGridService extends PubSubService { const viewports = []; for (const viewport of props) { - const updatedViewport = state.viewports[viewport.viewportIndex]; + const updatedViewport = state.viewports.get(viewport.viewportId); if (updatedViewport) { viewports.push(updatedViewport); } else { - console.warn( - "ViewportGridService::Didn't find updated viewport", - viewport - ); + console.warn("ViewportGridService::Didn't find updated viewport", viewport); } } this._broadcastEvent(ViewportGridService.EVENTS.GRID_STATE_CHANGED, { @@ -118,7 +116,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType = 'grid', - activeViewportIndex = undefined, + activeViewportId = undefined, findOrCreateViewport = undefined, }) { this.serviceImplementation._setLayout({ @@ -126,7 +124,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType, - activeViewportIndex, + activeViewportId, findOrCreateViewport, }); this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { @@ -160,8 +158,10 @@ class ViewportGridService extends PubSubService { return this.serviceImplementation._getNumViewportPanes(); } - public getLayoutOptionsFromState(state) { - return state.viewports.map(viewport => { + public getLayoutOptionsFromState( + state: any + ): { x: number; y: number; width: number; height: number }[] { + return Array.from(state.viewports.entries()).map(([_, viewport]) => { return { x: viewport.x, y: viewport.y, diff --git a/platform/core/src/services/ViewportGridService/getPresentationIds.ts b/platform/core/src/services/ViewportGridService/getPresentationIds.ts index 4299175115a..47771c300a8 100644 --- a/platform/core/src/services/ViewportGridService/getPresentationIds.ts +++ b/platform/core/src/services/ViewportGridService/getPresentationIds.ts @@ -10,8 +10,14 @@ const DEFAULT = 'default'; // dragged and dropped the view in twice. For example, it allows displaying // bone, brain and soft tissue views of a single display set, and to still // remember the specific changes to each viewport. -const addUniqueIndex = (arr, key, viewports) => { +const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => { arr.push(0); + + // If we are updating the viewport, we should not increment the index + if (isUpdatingSameViewport) { + return; + } + // The 128 is just a value that is larger than how many viewports we // display at once, used as an upper bound on how many unique presentation // ID's might exist for a single display set at once. @@ -19,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports) => { arr[arr.length - 1] = displayInstance; const testId = arr.join(JOIN_STR); if ( - !viewports.find( + !Array.from(viewports.values()).find( viewport => viewport.viewportOptions?.presentationIds?.[key] === testId ) ) { @@ -29,10 +35,16 @@ const addUniqueIndex = (arr, key, viewports) => { }; const getLutId = (ds): string => { - if (!ds || !ds.options) return DEFAULT; - if (ds.options.id) return ds.options.id; + if (!ds || !ds.options) { + return DEFAULT; + } + if (ds.options.id) { + return ds.options.id; + } const arr = Object.entries(ds.options).map(([key, val]) => `${key}=${val}`); - if (!arr.length) return DEFAULT; + if (!arr.length) { + return DEFAULT; + } return arr.join(JOIN_STR); }; @@ -87,12 +99,10 @@ export type PresentationIds = { * @returns PresentationIds */ const getPresentationIds = (viewport, viewports): PresentationIds => { - if (!viewport) return; - const { - viewportOptions, - displaySetInstanceUIDs, - displaySetOptions, - } = viewport; + if (!viewport) { + return; + } + const { viewportOptions, displaySetInstanceUIDs, displaySetOptions } = viewport; if (!viewportOptions || !displaySetInstanceUIDs?.length) { return; } @@ -102,15 +112,33 @@ const getPresentationIds = (viewport, viewports): PresentationIds => { const lutPresentationArr = [lutId]; const positionPresentationArr = [orientation || 'acquisition']; - if (id) positionPresentationArr.push(id); + if (id) { + positionPresentationArr.push(id); + } for (const uid of displaySetInstanceUIDs) { positionPresentationArr.push(uid); lutPresentationArr.push(uid); } - addUniqueIndex(positionPresentationArr, 'positionPresentationId', viewports); - addUniqueIndex(lutPresentationArr, 'lutPresentationId', viewports); + // only add unique index if the viewport is getting inserted and not updated + const isUpdatingSameViewport = Array.from(viewports.values()).some(v => { + return ( + v.displaySetInstanceUIDs.toString() === viewport.displaySetInstanceUIDs.toString() && + v.viewportId === viewport.viewportId + ); + }); + + // if it is updating the viewport we should not increment the index since + // it might be a layer on the fusion or a SEG layer that is added on + // top of the original display set + addUniqueIndex( + positionPresentationArr, + 'positionPresentationId', + viewports, + isUpdatingSameViewport + ); + addUniqueIndex(lutPresentationArr, 'lutPresentationId', viewports, isUpdatingSameViewport); const lutPresentationId = lutPresentationArr.join(JOIN_STR); const positionPresentationId = positionPresentationArr.join(JOIN_STR); diff --git a/platform/core/src/services/_shared/pubSubServiceInterface.js b/platform/core/src/services/_shared/pubSubServiceInterface.ts similarity index 90% rename from platform/core/src/services/_shared/pubSubServiceInterface.js rename to platform/core/src/services/_shared/pubSubServiceInterface.ts index 7d4460fda27..822f969af59 100644 --- a/platform/core/src/services/_shared/pubSubServiceInterface.js +++ b/platform/core/src/services/_shared/pubSubServiceInterface.ts @@ -89,6 +89,13 @@ function _broadcastEvent(eventName, callbackProps) { /** Export a PubSubService class to be used instead of the individual items */ export class PubSubService { + EVENTS: any; + subscribe: (eventName: string, callback: Function) => { unsubscribe: () => any; }; + _broadcastEvent: (eventName: string, callbackProps: any) => void; + _unsubscribe: (eventName: string, listenerId: string) => void; + _isValidEvent: (eventName: string) => boolean; + listeners: {}; + unsubscriptions: any[]; constructor(EVENTS) { this.EVENTS = EVENTS; this.subscribe = subscribe; @@ -117,6 +124,6 @@ export class PubSubService { consume: function Consume() { this.isConsumed = true; }, - } + }; } } diff --git a/platform/core/src/services/index.ts b/platform/core/src/services/index.ts index b34e028d8e3..ab127fb9748 100644 --- a/platform/core/src/services/index.ts +++ b/platform/core/src/services/index.ts @@ -10,9 +10,7 @@ import ToolbarService from './ToolBarService'; import ViewportGridService from './ViewportGridService'; import CineService from './CineService'; import HangingProtocolService from './HangingProtocolService'; -import pubSubServiceInterface, { - PubSubService, -} from './_shared/pubSubServiceInterface'; +import pubSubServiceInterface, { PubSubService } from './_shared/pubSubServiceInterface'; import UserAuthenticationService from './UserAuthenticationService'; import CustomizationService from './CustomizationService'; diff --git a/platform/core/src/string.js b/platform/core/src/string.js index b9ba46052f1..0c841a90991 100644 --- a/platform/core/src/string.js +++ b/platform/core/src/string.js @@ -1,8 +1,5 @@ function isObject(subject) { - return ( - subject instanceof Object || - (typeof subject === 'object' && subject !== null) - ); + return subject instanceof Object || (typeof subject === 'object' && subject !== null); } function isString(subject) { diff --git a/platform/core/src/types/Command.ts b/platform/core/src/types/Command.ts index 837c835907e..8a8b8e921ed 100644 --- a/platform/core/src/types/Command.ts +++ b/platform/core/src/types/Command.ts @@ -4,7 +4,9 @@ export interface Command { context?: string; } +export type RunCommand = Command | Command[]; + /** A set of commands, typically contained in a tool item or other configuration */ export interface Commands { - commands: Command[]; + commands: RunCommand; } diff --git a/platform/core/src/types/DataSource.ts b/platform/core/src/types/DataSource.ts new file mode 100644 index 00000000000..cc27e16302a --- /dev/null +++ b/platform/core/src/types/DataSource.ts @@ -0,0 +1,7 @@ +export type DataSourceDefinition = { + // TODO friendlyName to move to configuration; here now for legacy purposes + friendlyName: string; + namespace: string; + sourceName: string; + configuration: any; +}; diff --git a/platform/core/src/types/DataSourceConfigurationAPI.ts b/platform/core/src/types/DataSourceConfigurationAPI.ts new file mode 100644 index 00000000000..146f4a2851f --- /dev/null +++ b/platform/core/src/types/DataSourceConfigurationAPI.ts @@ -0,0 +1,68 @@ +export interface BaseDataSourceConfigurationAPIItem { + id: string; + name: string; +} + +/** + * The interface to use to configure an associated data source. Typically an + * instance of this interface is associated with a data source that the instance + * understands and can alter the data source's configuration. + */ +export interface BaseDataSourceConfigurationAPI { + /** + * Gets the i18n labels (i.e. the i18n lookup keys) for each of the configurable items + * of the data source configuration API. + * For example, for the Google Cloud Healthcare API, this would be + * ['Project', 'Location', 'Data set', 'DICOM store']. + * Besides the configurable item labels themselves, several other string look ups + * are used base on EACH of the labels returned by this method. + * For instance, for the label {itemLabel}, the following strings are fetched for + * translation... + * 1. `No {itemLabel} available` + * - used to indicate no such items are available + * - for example, for Google, `No Project available` would be 'No projects available' + * 2. `Select {itemLabel}` + * - used to direct selection of the item + * - for example, for Google, `Select Project` would be 'Select a project' + * 3. `Error fetching {itemLabel} list` + * - used to indicate an error occurred fetching the list of items + * - usually accompanied by the error itself + * - for example, for Google, `Error fetching Project list` would be 'Error fetching projects' + * 4. `Search {itemLabel} list` + * - used as the placeholder text for filtering a list of items + * - for example, for Google, `Search Project list` would be 'Search projects' + */ + getItemLabels(): Array; + + /** + * Initializes the data source configuration API and returns the top-level sub-items + * that can be chosen to begin the process of configuring the data source. + * For example, for the Google Cloud Healthcare API, this would perform the initial request + * to fetch the top level projects for the logged in user account. + */ + initialize(): Promise>; + + /** + * Sets the current path item and returns the sub-items of that item + * that can be further chosen to configure a data source. + * When setting the last configurable item of the data source (path), this method + * returns an empty list AND configures the active data source with the selected + * items path. + * For example, for the Google Cloud Healthcare API, this would take the current item + * (say a data set) and queries and returns its sub-items (i.e. all of the DICOM stores + * contained in that data set). Furthermore, whenever the item to set is a DICOM store, + * the Google Cloud Healthcare API implementation would update the OHIF data source + * associated with this instance to point to that DICOM store. + * @param item the item to set as current + */ + setCurrentItem( + item: BaseDataSourceConfigurationAPIItem + ): Promise>; + + /** + * Gets the list of items currently configured for the data source associated with + * this API instance. The resultant array must be the same length as the result of + * `getItemLabels`. + */ + getConfiguredItems(): Promise>; +} diff --git a/platform/core/src/types/DisplaySet.ts b/platform/core/src/types/DisplaySet.ts new file mode 100644 index 00000000000..3bc1cb51676 --- /dev/null +++ b/platform/core/src/types/DisplaySet.ts @@ -0,0 +1,15 @@ +import { InstanceMetadata } from './StudyMetadata'; + +export type DisplaySet = { + displaySetInstanceUID: string; + instances: InstanceMetadata[]; + StudyInstanceUID: string; + SeriesInstanceUID?: string; + numImages?: number; + unsupported?: boolean; +}; + +export type DisplaySetSeriesMetadataInvalidatedEvent = { + displaySetInstanceUID: string; + invalidateData: boolean; +}; diff --git a/platform/core/src/types/HangingProtocol.ts b/platform/core/src/types/HangingProtocol.ts index fec29f4896e..797028f3815 100644 --- a/platform/core/src/types/HangingProtocol.ts +++ b/platform/core/src/types/HangingProtocol.ts @@ -61,7 +61,7 @@ export type SetProtocolOptions = { export type HangingProtocolMatchDetails = { displaySetMatchDetails: Map; - viewportMatchDetails: Map; + viewportMatchDetails: Map; }; export type ConstraintValue = @@ -257,12 +257,14 @@ export type ProtocolNotifications = { * It is a set of rules about when the protocol can be applied at all, * as well as a set of stages that represent indivividual views. * Additionally, the display set selectors are used to choose from the existing - * display sets. The hanging protcol definition here does NOT allow + * display sets. The hanging protocol definition here does NOT allow * redefining the display sets to use, but only selects the views to show. */ export type Protocol = { // Mandatory id: string; + /** A description of this protocol. Used as a tool tip for the user. */ + description?: string; /** Maps ids to display set selectors to choose display sets */ displaySetSelectors: Record; /** A default viewport to use for any stage to select new viewport layouts. */ @@ -270,7 +272,6 @@ export type Protocol = { stages: ProtocolStage[]; // Optional locked?: boolean; - hasUpdatedPriorsInformation?: boolean; name?: string; createdDate?: string; modifiedDate?: string; @@ -284,7 +285,9 @@ export type Protocol = { /* The number of priors required for this hanging protocol. * -1 means that NO priors are referenced, and thus this HP matches * only the active study, whereas 0 means that an unknown number of - * priors is matched. + * priors is matched. Positive values mean at least that many priors are + * required. + * Replaces hasUpdatedPriors */ numberOfPriorsReferenced?: number; syncDataForViewports?: boolean; @@ -295,10 +298,7 @@ export type Protocol = { * to the GUI when this is used, and it can be expensive to apply. * Alternatives include using the custom attributes where possible. */ -export type ProtocolGenerator = ({ - servicesManager: any, - commandsManager: any, -}) => { +export type ProtocolGenerator = ({ servicesManager: any, commandsManager: any }) => { protocol: Protocol; }; diff --git a/platform/core/src/types/IPubSub.ts b/platform/core/src/types/IPubSub.ts index 06e918374da..733c98146e6 100644 --- a/platform/core/src/types/IPubSub.ts +++ b/platform/core/src/types/IPubSub.ts @@ -2,10 +2,7 @@ import Consumer from './Consumer'; export default interface IPubSub { subscribe: (eventName: string, callback: Consumer) => void; - _broadcastEvent: ( - eventName: string, - callbackProps: Record - ) => void; + _broadcastEvent: (eventName: string, callbackProps: Record) => void; _unsubscribe: (eventName: string, listenerId: string) => void; _isValidEvent: (eventName: string) => boolean; } diff --git a/platform/core/src/types/PanelModule.ts b/platform/core/src/types/PanelModule.ts index fe3e5013900..eb9d13e4326 100644 --- a/platform/core/src/types/PanelModule.ts +++ b/platform/core/src/types/PanelModule.ts @@ -1,4 +1,4 @@ -import { PubSubService } from "../services"; +import { PubSubService } from '../services'; type Panel = { id?: string; @@ -11,8 +11,8 @@ type Panel = { type ActivatePanelTriggers = { sourcePubSubService: PubSubService; - sourceEvents: string[] -} + sourceEvents: string[]; +}; interface PanelEvent { panelId: string; @@ -22,9 +22,4 @@ interface ActivatePanelEvent extends PanelEvent { forceActive: boolean; } -export type { - ActivatePanelEvent, - ActivatePanelTriggers, - Panel, - PanelEvent, -}; +export type { ActivatePanelEvent, ActivatePanelTriggers, Panel, PanelEvent }; diff --git a/platform/core/src/types/Services.ts b/platform/core/src/types/Services.ts index abc6b09beaf..ece05c32a95 100644 --- a/platform/core/src/types/Services.ts +++ b/platform/core/src/types/Services.ts @@ -14,23 +14,23 @@ import { * The interface for the services object */ export default interface Services { - userAuthenticationService?: Record; hangingProtocolService?: HangingProtocolService; customizationService?: CustomizationService; measurementService?: MeasurementService; displaySetService?: DisplaySetService; - cineService?: Record; toolbarService?: ToolbarService; - cornerstoneViewportService?: Record; - uiDialogService?: Record; - toolGroupService?: Record; - uiNotificationService?: UINotificationService; - uiModalService?: UIModalService; - uiViewportDialogService?: Record; viewportGridService?: ViewportGridService; - syncGroupService?: Record; - cornerstoneCacheService?: Record; - segmentationService?: Record; + uiModalService?: UIModalService; + uiNotificationService?: UINotificationService; stateSyncService?: StateSyncService; - panelService?: Record; + cineService?: unknown; + userAuthenticationService?: unknown; + cornerstoneViewportService?: unknown; + uiDialogService?: unknown; + toolGroupService?: unknown; + uiViewportDialogService?: unknown; + syncGroupService?: unknown; + cornerstoneCacheService?: unknown; + segmentationService?: unknown; + panelService?: unknown; } diff --git a/platform/core/src/types/TimingEnum.ts b/platform/core/src/types/TimingEnum.ts new file mode 100644 index 00000000000..eddbc55d0a9 --- /dev/null +++ b/platform/core/src/types/TimingEnum.ts @@ -0,0 +1,24 @@ +export enum TimingEnum { + // The time from when the users selects a study until the study metadata + // is loaded (and the display sets are ready) + STUDY_TO_DISPLAY_SETS = 'studyToDisplaySetsLoaded', + + // The time from when the user selects a study until any viewport renders + STUDY_TO_FIRST_IMAGE = 'studyToFirstImage', + + // The time from when display sets are loaded until any viewport renders + // an image. + DISPLAY_SETS_TO_FIRST_IMAGE = 'displaySetsToFirstImage', + + // The time from when display sets are loaded until all viewports have images + DISPLAY_SETS_TO_ALL_IMAGES = 'displaySetsToAllImages', + + // The time from when the user hits search until the worklist is displayed + SEARCH_TO_LIST = 'searchToList', + + // The time from when the html script first starts being evaluated (before + // any other scripts or CSS is loaded), until the time that the first image + // is viewed for viewer endpoints, or the time that the first search for studies + // completes. + SCRIPT_TO_VIEW = 'scriptToView', +} diff --git a/platform/core/src/types/index.ts b/platform/core/src/types/index.ts index 3c68b60c422..efcc8ec3ccf 100644 --- a/platform/core/src/types/index.ts +++ b/platform/core/src/types/index.ts @@ -1,21 +1,37 @@ -import * as Extensions from '../extensions/ExtensionManager'; -import * as HangingProtocol from './HangingProtocol'; -import Services from './Services'; -import Hotkey from '../classes/Hotkey'; -import { DisplaySet } from '../services/DisplaySetService/DisplaySetService'; +import type * as Extensions from '../extensions/ExtensionManager'; +import type * as HangingProtocol from './HangingProtocol'; +import type Services from './Services'; +import type Hotkey from '../classes/Hotkey'; +import type { DataSourceDefinition } from './DataSource'; +import type { + BaseDataSourceConfigurationAPI, + BaseDataSourceConfigurationAPIItem, +} from './DataSourceConfigurationAPI'; -export * from '../services/CustomizationService/types'; +export type * from '../services/CustomizationService/types'; // Separate out some generic types -export * from './AppConfig'; -export * from './Consumer'; -export * from './Command'; -export * from './StudyMetadata'; -export * from './PanelModule'; -export * from './IPubSub'; -export * from './Color'; +export type * from './AppConfig'; +export type * from './Consumer'; +export type * from './Command'; +export type * from './DisplaySet'; +export type * from './StudyMetadata'; +export type * from './PanelModule'; +export type * from './IPubSub'; +export type * from './Color'; + +// Enum exports +export * from './TimingEnum'; /** * Export the types used within the various services and managers, but * not the services/managers themselves, which are exported at the top level. */ -export { Extensions, HangingProtocol, Services, Hotkey, DisplaySet }; +export { + Extensions, + HangingProtocol, + Services, + Hotkey, + DataSourceDefinition, + BaseDataSourceConfigurationAPI, + BaseDataSourceConfigurationAPIItem, +}; diff --git a/platform/core/src/utils/Queue.js b/platform/core/src/utils/Queue.js index afe64cc00a8..34149bcfce3 100644 --- a/platform/core/src/utils/Queue.js +++ b/platform/core/src/utils/Queue.js @@ -8,7 +8,7 @@ export default class Queue { /** * Creates a new "proxy" function associated with the current execution queue * instance. When the returned function is invoked, the queue limit is checked - * to make sure the limit of scheduled tasks is repected (throwing an + * to make sure the limit of scheduled tasks is respected (throwing an * exception when the limit has been reached and before calling the original * function). The original function is only invoked after all the previously * scheduled tasks have finished executing (their returned promises have diff --git a/platform/core/src/utils/absoluteUrl.js b/platform/core/src/utils/absoluteUrl.js index faf2369be7c..7fb9fd9168a 100644 --- a/platform/core/src/utils/absoluteUrl.js +++ b/platform/core/src/utils/absoluteUrl.js @@ -1,7 +1,9 @@ const absoluteUrl = path => { let absolutePath = '/'; - if (!path) return absolutePath; + if (!path) { + return absolutePath; + } // TODO: Find another way to get root url const absoluteUrl = window.location.origin; diff --git a/platform/core/src/utils/absoluteUrl.test.js b/platform/core/src/utils/absoluteUrl.test.js index fe923064d97..41cc64a1d30 100644 --- a/platform/core/src/utils/absoluteUrl.test.js +++ b/platform/core/src/utils/absoluteUrl.test.js @@ -13,9 +13,7 @@ describe('absoluteUrl', () => { writable: true, }); const absoluteUrlOutput = absoluteUrl('/path_3/path_to_destination'); - expect(absoluteUrlOutput).toEqual( - '/path_1/path_2/path_3/path_to_destination' - ); + expect(absoluteUrlOutput).toEqual('/path_1/path_2/path_3/path_to_destination'); }); test('should return / when the path is not defined', () => { diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts index 933437a2460..087f97b4224 100644 --- a/platform/core/src/utils/combineFrameInstance.ts +++ b/platform/core/src/utils/combineFrameInstance.ts @@ -9,42 +9,34 @@ * single frame data. (eg frame is undefined is the same as frame===1). */ const combineFrameInstance = (frame, instance) => { - const { - PerFrameFunctionalGroupsSequence, - SharedFunctionalGroupsSequence, - NumberOfFrames, - } = instance; + const { PerFrameFunctionalGroupsSequence, SharedFunctionalGroupsSequence, NumberOfFrames } = + instance; if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { const frameNumber = Number.parseInt(frame || 1); - const shared = (SharedFunctionalGroupsSequence - ? Object.values(SharedFunctionalGroupsSequence[0]) - : [] + const shared = ( + SharedFunctionalGroupsSequence ? Object.values(SharedFunctionalGroupsSequence[0]) : [] ) + .filter(it => !!it) .map(it => it[0]) .filter(it => it !== undefined && typeof it === 'object'); - const perFrame = (PerFrameFunctionalGroupsSequence - ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) - : [] + const perFrame = ( + PerFrameFunctionalGroupsSequence + ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) + : [] ) + .filter(it => !!it) .map(it => it[0]) .filter(it => it !== undefined && typeof it === 'object'); // this is to fix NM multiframe datasets with position and orientation // information inside DetectorInformationSequence - if ( - !instance.ImageOrientationPatient && - instance.DetectorInformationSequence - ) { + if (!instance.ImageOrientationPatient && instance.DetectorInformationSequence) { instance.ImageOrientationPatient = instance.DetectorInformationSequence[0].ImageOrientationPatient; } - if ( - !instance.ImagePositionPatient && - instance.DetectorInformationSequence - ) { - instance.ImagePositionPatient = - instance.DetectorInformationSequence[0].ImagePositionPatient; + if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) { + instance.ImagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient; } const newInstance = Object.assign(instance, { frameNumber: frameNumber }); diff --git a/platform/core/src/utils/debounce.js b/platform/core/src/utils/debounce.js index 83a16b0990a..7a5b2bdf3c7 100644 --- a/platform/core/src/utils/debounce.js +++ b/platform/core/src/utils/debounce.js @@ -4,17 +4,21 @@ // leading edge, instead of the trailing. function debounce(func, wait, immediate) { var timeout; - return function() { + return function () { var context = this, args = arguments; - var later = function() { + var later = function () { timeout = null; - if (!immediate) func.apply(context, args); + if (!immediate) { + func.apply(context, args); + } }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); + if (callNow) { + func.apply(context, args); + } }; } diff --git a/platform/core/src/utils/downloadCSVReport.js b/platform/core/src/utils/downloadCSVReport.js index 12951a6a646..b2da98b6126 100644 --- a/platform/core/src/utils/downloadCSVReport.js +++ b/platform/core/src/utils/downloadCSVReport.js @@ -17,22 +17,14 @@ export default function downloadCSVReport(measurementData) { const reportMap = {}; measurementData.forEach(measurement => { - const { - referenceStudyUID, - referenceSeriesUID, - getReport, - uid, - } = measurement; + const { referenceStudyUID, referenceSeriesUID, getReport, uid } = measurement; if (!getReport) { console.warn('Measurement does not have a getReport function'); return; } - const seriesMetadata = DicomMetadataStore.getSeries( - referenceStudyUID, - referenceSeriesUID - ); + const seriesMetadata = DicomMetadataStore.getSeries(referenceStudyUID, referenceSeriesUID); const commonRowItems = _getCommonRowItems(measurement, seriesMetadata); const report = getReport(measurement); @@ -57,9 +49,7 @@ export default function downloadCSVReport(measurementData) { const results = _mapReportsToRowArray(reportMap, columns); - let csvContent = - 'data:text/csv;charset=utf-8,' + - results.map(res => res.join(',')).join('\n'); + let csvContent = 'data:text/csv;charset=utf-8,' + results.map(res => res.join(',')).join('\n'); _createAndDownloadFile(csvContent); } diff --git a/platform/core/src/utils/formatDate.js b/platform/core/src/utils/formatDate.js index a2a9a9bab3a..7b44277f200 100644 --- a/platform/core/src/utils/formatDate.js +++ b/platform/core/src/utils/formatDate.js @@ -8,5 +8,6 @@ import moment from 'moment'; * @returns {string} Formatted date */ export default (date, format = 'DD-MMM-YYYY') => { - return moment(date).format(format); + // moment(undefined) returns the current date, so return the empty string instead + return date ? moment(date).format(format) : ''; }; diff --git a/platform/core/src/utils/formatPN.js b/platform/core/src/utils/formatPN.js index 5e6f26a029a..04270040a65 100644 --- a/platform/core/src/utils/formatPN.js +++ b/platform/core/src/utils/formatPN.js @@ -6,7 +6,10 @@ export default function formatPN(name) { return; } - const nameToUse = name.Alphabetic ?? name; + let nameToUse = name.Alphabetic ?? name; + if (typeof nameToUse === 'object') { + nameToUse = ''; + } // Convert the first ^ to a ', '. String.replace() only affects // the first appearance of the character. diff --git a/platform/core/src/utils/generateAcceptHeader.ts b/platform/core/src/utils/generateAcceptHeader.ts new file mode 100644 index 00000000000..a428be0f73d --- /dev/null +++ b/platform/core/src/utils/generateAcceptHeader.ts @@ -0,0 +1,57 @@ +const generateAcceptHeader = ( + configAcceptHeader = [], + requestTransferSyntaxUID = null, + omitQuotationForMultipartRequest = false +): string[] => { + //if acceptedHeader is passed by config use it as it. + if (configAcceptHeader.length > 0) { + return configAcceptHeader; + } + + let acceptHeader = ['multipart/related']; + if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) { + const type = typeForTS[requestTransferSyntaxUID]; + acceptHeader.push('type=' + type); + acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID); + } else { + acceptHeader.push('type=application/octet-stream'); + } + + if (!omitQuotationForMultipartRequest) { + //need to add quotation for each mime type of each accept entry + acceptHeader = acceptHeader.map(mime => { + if (mime.startsWith('type=')) { + const quotedParam = 'type="' + mime.substring(5, mime.length) + '"'; + return quotedParam; + } + if (mime.startsWith('transfer-syntax=')) { + const quotedParam = 'transfer-syntax="' + mime.substring(16, mime.length) + '"'; + return quotedParam; + } else { + return mime; + } + }); + } + + return [acceptHeader.join('; ')]; +}; + +const typeForTS = { + '*': 'application/octet-stream', + '1.2.840.10008.1.2.1': 'application/octet-stream', + '1.2.840.10008.1.2': 'application/octet-stream', + '1.2.840.10008.1.2.2': 'application/octet-stream', + '1.2.840.10008.1.2.4.70': 'image/jpeg', + '1.2.840.10008.1.2.4.50': 'image/jpeg', + '1.2.840.10008.1.2.4.51': 'image/dicom+jpeg', + '1.2.840.10008.1.2.4.57': 'image/jpeg', + '1.2.840.10008.1.2.5': 'image/dicom-rle', + '1.2.840.10008.1.2.4.80': 'image/jls', + '1.2.840.10008.1.2.4.81': 'image/jls', + '1.2.840.10008.1.2.4.90': 'image/jp2', + '1.2.840.10008.1.2.4.91': 'image/jp2', + '1.2.840.10008.1.2.4.92': 'image/jpx', + '1.2.840.10008.1.2.4.93': 'image/jpx', +}; + +export default generateAcceptHeader; diff --git a/platform/core/src/utils/getImageId.js b/platform/core/src/utils/getImageId.js index ccd5e39171d..51e057b9125 100644 --- a/platform/core/src/utils/getImageId.js +++ b/platform/core/src/utils/getImageId.js @@ -38,11 +38,7 @@ export default function getImageId(instance, frame, thumbnail = false) { const renderingAttr = thumbnail ? 'thumbnailRendering' : 'imageRendering'; - if ( - !instance[renderingAttr] || - instance[renderingAttr] === 'wadouri' || - !instance.wadorsuri - ) { + if (!instance[renderingAttr] || instance[renderingAttr] === 'wadouri' || !instance.wadorsuri) { let imageId = 'dicomweb:' + instance.wadouri; if (frame !== undefined) { imageId += '&frame=' + frame; diff --git a/platform/core/src/utils/hierarchicalListUtils.js b/platform/core/src/utils/hierarchicalListUtils.js index 5c3d5d3b283..ca12c7752c4 100644 --- a/platform/core/src/utils/hierarchicalListUtils.js +++ b/platform/core/src/utils/hierarchicalListUtils.js @@ -36,11 +36,11 @@ function addToList(list, ...values) { * once for each leaf-node of the tree. The ancestors of the leaf-node being * visited are passed to the callback function along with the leaf-node in * the exact same order they appear on the tree (from root to leaf); - * @ For example, if the hierachy `a > b > c` appears on the tree ("a" being + * @ For example, if the hierarchy `a > b > c` appears on the tree ("a" being * the root and "c" being the leaf) the callback function will be called as: * callback('a', 'b', 'c'); * @param {Array} list The hierarchical list to be iterated - * @param {function} callback The callback which will be exected once for + * @param {function} callback The callback which will be executed once for * each leaf-node of the hierarchical list; * @returns {Array} Returns the provided list or null for bad arguments; */ @@ -103,7 +103,7 @@ function print(list) { let text = ''; if (Array.isArray(list)) { let prev = []; - forEachValue(list, function(...args) { + forEachValue(list, function (...args) { let prevLen = prev.length; for (let i = 0, l = args.length; i < l; ++i) { if (i < prevLen && args[i] === prev[i]) { diff --git a/platform/core/src/utils/hierarchicalListUtils.test.js b/platform/core/src/utils/hierarchicalListUtils.test.js index 4141783391d..fbf41e9fa87 100644 --- a/platform/core/src/utils/hierarchicalListUtils.test.js +++ b/platform/core/src/utils/hierarchicalListUtils.test.js @@ -1,9 +1,9 @@ import { addToList, forEach, getItem, print } from './hierarchicalListUtils'; -describe('hierarchicalListUtils', function() { +describe('hierarchicalListUtils', function () { let sharedList; - beforeEach(function() { + beforeEach(function () { sharedList = [ ['1.2.3.1', ['1.2.3.1.1', '1.2.3.1.2']], '1.2.3.2', @@ -11,14 +11,14 @@ describe('hierarchicalListUtils', function() { ]; }); - describe('getItem', function() { - it('should retrieve elements from a list by index', function() { + describe('getItem', function () { + it('should retrieve elements from a list by index', function () { expect(getItem(sharedList, 0)).toBe('1.2.3.1'); expect(getItem(sharedList, 1)).toBe('1.2.3.2'); expect(getItem(sharedList, 2)).toBe('1.2.3.3'); expect(getItem(sharedList, 3)).toBeUndefined(); }); - it('should retrieve elements from a list by path', function() { + it('should retrieve elements from a list by path', function () { expect(getItem(sharedList, '0')).toBe('1.2.3.1'); expect(getItem(sharedList, '0/0')).toBe('1.2.3.1.1'); expect(getItem(sharedList, '0/1')).toBe('1.2.3.1.2'); @@ -35,8 +35,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('addToList', function() { - it('should support adding elements to a list hierarchically', function() { + describe('addToList', function () { + it('should support adding elements to a list hierarchically', function () { const list = []; addToList(list, '1.2.3.1', '1.2.3.1.1'); addToList(list, '1.2.3.1', '1.2.3.1.2'); @@ -46,7 +46,7 @@ describe('hierarchicalListUtils', function() { addToList(list, '1.2.3.3', '1.2.3.3.2', '1.2.3.3.2.2'); expect(list).toStrictEqual(sharedList); }); - it('should change leaf nodes into non-leaf nodes', function() { + it('should change leaf nodes into non-leaf nodes', function () { const listw = []; const listx = [['x.1', ['x.1.1', 'x.1.2']], 'x.2']; const listy = [ @@ -64,8 +64,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('forEach', function() { - it('should iterate through all leaf nodes of the tree', function() { + describe('forEach', function () { + it('should iterate through all leaf nodes of the tree', function () { const fn = jest.fn(); forEach(sharedList, fn); expect(fn).toHaveBeenCalledTimes(6); @@ -78,8 +78,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('print', function() { - it('should pretty-print the hierarchical list', function() { + describe('print', function () { + it('should pretty-print the hierarchical list', function () { expect(print(sharedList)).toBe( '1.2.3.1\n' + ' 1.2.3.1.1\n' + diff --git a/platform/core/src/utils/hotkeys/pausePlugin.js b/platform/core/src/utils/hotkeys/pausePlugin.js index 6774d760f7d..82176e0619d 100644 --- a/platform/core/src/utils/hotkeys/pausePlugin.js +++ b/platform/core/src/utils/hotkeys/pausePlugin.js @@ -8,7 +8,7 @@ export default function pausePlugin(Mousetrap) { var _originalStopCallback = Mousetrap.prototype.stopCallback; - Mousetrap.prototype.stopCallback = function(e, element, combo) { + Mousetrap.prototype.stopCallback = function (e, element, combo) { var self = this; if (self.paused) { @@ -18,12 +18,12 @@ export default function pausePlugin(Mousetrap) { return _originalStopCallback.call(self, e, element, combo); }; - Mousetrap.prototype.pause = function() { + Mousetrap.prototype.pause = function () { var self = this; self.paused = true; }; - Mousetrap.prototype.unpause = function() { + Mousetrap.prototype.unpause = function () { var self = this; self.paused = false; }; diff --git a/platform/core/src/utils/hotkeys/recordPlugin.js b/platform/core/src/utils/hotkeys/recordPlugin.js index e425ce5bfe2..a2b757442d6 100644 --- a/platform/core/src/utils/hotkeys/recordPlugin.js +++ b/platform/core/src/utils/hotkeys/recordPlugin.js @@ -122,7 +122,7 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { */ function _normalizeSequence(sequence) { for (let i = 0; i < sequence.length; ++i) { - sequence[i].sort(function(x, y) { + sequence[i].sort(function (x, y) { // modifier keys always come first, in alphabetical order if (x.length > 1 && y.length === 1) { return -1; @@ -177,10 +177,10 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.record = function(callback) { + Mousetrap.prototype.record = function (callback) { var self = this; self.recording = true; - _recordedSequenceCallback = function() { + _recordedSequenceCallback = function () { self.recording = false; callback.apply(self, arguments); }; @@ -192,7 +192,7 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.stopRecord = function() { + Mousetrap.prototype.stopRecord = function () { var self = this; self.recording = false; }; @@ -203,11 +203,11 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.startRecording = function() { + Mousetrap.prototype.startRecording = function () { var self = this; self.recording = true; }; - Mousetrap.prototype.handleKey = function() { + Mousetrap.prototype.handleKey = function () { var self = this; _handleKey.apply(self, arguments); }; diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 06e7537f3b1..3f5d66e4132 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -1,6 +1,7 @@ import ObjectPath from './objectPath'; import absoluteUrl from './absoluteUrl'; import guid from './guid'; +import uuidv4 from './uuidv4'; import sortBy from './sortBy.js'; import writeScript from './writeScript.js'; import b64toBlob from './b64toBlob.js'; @@ -13,6 +14,7 @@ import Queue from './Queue'; import isDicomUid from './isDicomUid'; import formatDate from './formatDate'; import formatPN from './formatPN'; +import generateAcceptHeader from './generateAcceptHeader'; import resolveObjectPath from './resolveObjectPath'; import hierarchicalListUtils from './hierarchicalListUtils'; import progressTrackingUtils from './progressTrackingUtils'; @@ -40,6 +42,7 @@ import { splitComma, getSplitParam } from './splitComma'; const utils = { guid, + uuidv4, ObjectPath, absoluteUrl, sortBy, @@ -74,6 +77,7 @@ const utils = { subscribeToNextViewportGridChange, splitComma, getSplitParam, + generateAcceptHeader, }; export { @@ -105,6 +109,7 @@ export { downloadCSVReport, splitComma, getSplitParam, + generateAcceptHeader, }; export default utils; diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index b3c3e1e26e8..5ac07149c33 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -24,6 +24,7 @@ describe('Top level exports', () => { 'b64toBlob', 'formatDate', 'formatPN', + 'generateAcceptHeader', 'isEqualWithin', //'loadAndCacheDerivedDisplaySets', 'isDisplaySetReconstructable', @@ -38,6 +39,7 @@ describe('Top level exports', () => { 'hierarchicalListUtils', 'progressTrackingUtils', 'subscribeToNextViewportGridChange', + 'uuidv4', ].sort(); const exports = Object.keys(utils.default).sort(); diff --git a/platform/core/src/utils/isDicomUid.test.js b/platform/core/src/utils/isDicomUid.test.js index 3a442823a94..279a2b48742 100644 --- a/platform/core/src/utils/isDicomUid.test.js +++ b/platform/core/src/utils/isDicomUid.test.js @@ -1,13 +1,13 @@ import isDicomUid from './isDicomUid'; -describe('isDicomUid', function() { - it('should return true for valid DICOM UIDs', function() { +describe('isDicomUid', function () { + it('should return true for valid DICOM UIDs', function () { expect(isDicomUid('1')).toBe(true); expect(isDicomUid('1.2')).toBe(true); expect(isDicomUid('1.2.3')).toBe(true); expect(isDicomUid('1.2.3.4')).toBe(true); }); - it('should return false for invalid DICOM UIDs', function() { + it('should return false for invalid DICOM UIDs', function () { expect(isDicomUid('x')).toBe(false); expect(isDicomUid('1.')).toBe(false); expect(isDicomUid('1. 2')).toBe(false); diff --git a/platform/core/src/utils/isDisplaySetReconstructable.js b/platform/core/src/utils/isDisplaySetReconstructable.js index 373da994afc..8b348177d80 100644 --- a/platform/core/src/utils/isDisplaySetReconstructable.js +++ b/platform/core/src/utils/isDisplaySetReconstructable.js @@ -30,23 +30,17 @@ export default function isDisplaySetReconstructable(instances) { } // Can't reconstruct if all instances don't have the ImagePositionPatient. - if ( - !isMultiframe && - !instances.every(instance => instance.ImagePositionPatient) - ) { + if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) { return { value: false }; } const sortedInstances = sortInstancesByPosition(instances); - return isMultiframe - ? processMultiframe(sortedInstances[0]) - : processSingleframe(sortedInstances); + return isMultiframe ? processMultiframe(sortedInstances[0]) : processSingleframe(sortedInstances); } function hasPixelMeasurements(multiFrameInstance) { - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; const sharedSequence = multiFrameInstance.SharedFunctionalGroupsSequence; return ( @@ -54,39 +48,34 @@ function hasPixelMeasurements(multiFrameInstance) { Boolean(sharedSequence?.PixelMeasuresSequence) || Boolean( multiFrameInstance.PixelSpacing && - (multiFrameInstance.SliceThickness || - multiFrameInstance.SpacingBetweenFrames) + (multiFrameInstance.SliceThickness || multiFrameInstance.SpacingBetweenFrames) ) ); } function hasOrientation(multiFrameInstance) { const sharedSequence = multiFrameInstance.SharedFunctionalGroupsSequence; - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; return ( Boolean(sharedSequence?.PlaneOrientationSequence) || Boolean(perFrameSequence?.PlaneOrientationSequence) || Boolean( multiFrameInstance.ImageOrientationPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImageOrientationPatient + multiFrameInstance.DetectorInformationSequence?.[0]?.ImageOrientationPatient ) ); } function hasPosition(multiFrameInstance) { - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; return ( Boolean(perFrameSequence?.PlanePositionSequence) || Boolean(perFrameSequence?.CTPositionSequence) || Boolean( multiFrameInstance.ImagePositionPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImagePositionPatient + multiFrameInstance.DetectorInformationSequence?.[0]?.ImagePositionPatient ) ); } @@ -114,10 +103,7 @@ function processMultiframe(multiFrameInstance) { return { value: false }; } - if ( - multiFrameInstance.Modality.includes('NM') && - !isNMReconstructable(multiFrameInstance) - ) { + if (multiFrameInstance.Modality.includes('NM') && !isNMReconstructable(multiFrameInstance)) { return { value: false }; } @@ -130,9 +116,7 @@ function processSingleframe(instances) { const firstImageRows = toNumber(firstImage.Rows); const firstImageColumns = toNumber(firstImage.Columns); const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel); - const firstImageOrientationPatient = toNumber( - firstImage.ImageOrientationPatient - ); + const firstImageOrientationPatient = toNumber(firstImage.ImageOrientationPatient); const firstImagePositionPatient = toNumber(firstImage.ImagePositionPatient); // Can't reconstruct if we: @@ -141,12 +125,7 @@ function processSingleframe(instances) { // -- Have different orientations within a displaySet. for (let i = 1; i < instances.length; i++) { const instance = instances[i]; - const { - Rows, - Columns, - SamplesPerPixel, - ImageOrientationPatient, - } = instance; + const { Rows, Columns, SamplesPerPixel, ImageOrientationPatient } = instance; const imageOrientationPatient = toNumber(ImageOrientationPatient); @@ -161,23 +140,21 @@ function processSingleframe(instances) { } let missingFrames = 0; + let averageSpacingBetweenFrames; // Check if frame spacing is approximately equal within a spacingTolerance. // If spacing is on a uniform grid but we are missing frames, // Allow reconstruction, but pass back the number of missing frames. if (instances.length > 2) { - const lastIpp = toNumber( - instances[instances.length - 1].ImagePositionPatient - ); + const lastIpp = toNumber(instances[instances.length - 1].ImagePositionPatient); // We can't reconstruct if we are missing ImagePositionPatient values if (!firstImagePositionPatient || !lastIpp) { return { value: false }; } - const averageSpacingBetweenFrames = - _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / - (instances.length - 1); + averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (instances.length - 1); let previousImagePositionPatient = firstImagePositionPatient; @@ -190,10 +167,7 @@ function processSingleframe(instances) { imagePositionPatient, previousImagePositionPatient ); - const spacingIssue = _getSpacingIssue( - spacingBetweenFrames, - averageSpacingBetweenFrames - ); + const spacingIssue = _getSpacingIssue(spacingBetweenFrames, averageSpacingBetweenFrames); if (spacingIssue) { const issue = spacingIssue.issue; @@ -209,7 +183,7 @@ function processSingleframe(instances) { } } - return { value: true, missingFrames }; + return { value: true, averageSpacingBetweenFrames }; } function _isSameOrientation(iop1, iop2) { @@ -220,7 +194,10 @@ function _isSameOrientation(iop1, iop2) { return ( Math.abs(iop1[0] - iop2[0]) < iopTolerance && Math.abs(iop1[1] - iop2[1]) < iopTolerance && - Math.abs(iop1[2] - iop2[2]) < iopTolerance + Math.abs(iop1[2] - iop2[2]) < iopTolerance && + Math.abs(iop1[3] - iop2[3]) < iopTolerance && + Math.abs(iop1[4] - iop2[4]) < iopTolerance && + Math.abs(iop1[5] - iop2[5]) < iopTolerance ); } @@ -258,11 +235,7 @@ function _getSpacingIssue(spacing, averageSpacing) { } function _getPerpendicularDistance(a, b) { - return Math.sqrt( - Math.pow(a[0] - b[0], 2) + - Math.pow(a[1] - b[1], 2) + - Math.pow(a[2] - b[2], 2) - ); + return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)); } const constructableModalities = ['MR', 'CT', 'PT', 'NM']; @@ -270,3 +243,15 @@ const reconstructionIssues = { MISSING_FRAMES: 'missingframes', IRREGULAR_SPACING: 'irregularspacing', }; + +export { + hasPixelMeasurements, + hasOrientation, + hasPosition, + isNMReconstructable, + _isSameOrientation, + _getSpacingIssue, + _getPerpendicularDistance, + reconstructionIssues, + constructableModalities, +}; diff --git a/platform/core/src/utils/isImage.js b/platform/core/src/utils/isImage.js index 525e6ccd957..373abce9958 100644 --- a/platform/core/src/utils/isImage.js +++ b/platform/core/src/utils/isImage.js @@ -58,6 +58,8 @@ const imagesTypes = [ * @returns {boolean} - true if it has image data */ export const isImage = SOPClassUID => { - if (!SOPClassUID) return false; + if (!SOPClassUID) { + return false; + } return imagesTypes.indexOf(SOPClassUID) !== -1; }; diff --git a/platform/core/src/utils/isImage.test.js b/platform/core/src/utils/isImage.test.js index 1f53ccea177..88263920087 100644 --- a/platform/core/src/utils/isImage.test.js +++ b/platform/core/src/utils/isImage.test.js @@ -3,23 +3,17 @@ import { isImage } from './isImage'; describe('isImage', () => { test('should return true when the image is of type sopClassDictionary.ComputedRadiographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.ComputedRadiographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.ComputedRadiographyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForPresentation', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalXRayImageStorageForPresentation - ); + const isImageStatus = isImage(sopClassDictionary.DigitalXRayImageStorageForPresentation); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.DigitalXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -45,9 +39,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -62,16 +54,12 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedCTImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedCTImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedCTImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.UltrasoundMultiframeImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.UltrasoundMultiframeImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.UltrasoundMultiframeImageStorage); expect(isImageStatus).toBe(true); }); @@ -86,16 +74,12 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.EnhancedMRColorImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.EnhancedMRColorImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.EnhancedMRColorImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedMRImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedMRImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedMRImageStorage); expect(isImageStatus).toBe(true); }); @@ -105,9 +89,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.SecondaryCaptureImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.SecondaryCaptureImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.SecondaryCaptureImageStorage); expect(isImageStatus).toBe(true); }); @@ -140,9 +122,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRayAngiographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRayAngiographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRayAngiographicImageStorage); expect(isImageStatus).toBe(true); }); @@ -152,9 +132,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRayRadiofluoroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRayRadiofluoroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRayRadiofluoroscopicImageStorage); expect(isImageStatus).toBe(true); }); @@ -164,23 +142,17 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRay3DAngiographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRay3DAngiographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRay3DAngiographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.XRay3DCraniofacialImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRay3DCraniofacialImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRay3DCraniofacialImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.BreastTomosynthesisImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.BreastTomosynthesisImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.BreastTomosynthesisImageStorage); expect(isImageStatus).toBe(true); }); @@ -192,9 +164,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.BreastProjectionXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.BreastProjectionXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.BreastProjectionXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -213,9 +183,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.NuclearMedicineImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.NuclearMedicineImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.NuclearMedicineImageStorage); expect(isImageStatus).toBe(true); }); @@ -225,9 +193,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.VideoEndoscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoEndoscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoEndoscopicImageStorage); expect(isImageStatus).toBe(true); }); @@ -237,65 +203,47 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.VideoMicroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoMicroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoMicroscopicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLPhotographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLPhotographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLPhotographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VideoPhotographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoPhotographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoPhotographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography8BitImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicPhotography8BitImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicPhotography8BitImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography16BitImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicPhotography16BitImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicPhotography16BitImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicTomographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicTomographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicTomographyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLWholeSlideMicroscopyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLWholeSlideMicroscopyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLWholeSlideMicroscopyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.PositronEmissionTomographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.PositronEmissionTomographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.PositronEmissionTomographyImageStorage); expect(isImageStatus).toBe(true); }); @@ -305,9 +253,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedPETImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedPETImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedPETImageStorage); expect(isImageStatus).toBe(true); }); diff --git a/platform/core/src/utils/isLowPriorityModality.ts b/platform/core/src/utils/isLowPriorityModality.ts index aebb450ffb1..54024d5a051 100644 --- a/platform/core/src/utils/isLowPriorityModality.ts +++ b/platform/core/src/utils/isLowPriorityModality.ts @@ -1,10 +1,4 @@ -const LOW_PRIORITY_MODALITIES = Object.freeze([ - 'SEG', - 'KO', - 'PR', - 'SR', - 'RTSTRUCT', -]); +const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR', 'SR', 'RTSTRUCT']); export default function isLowPriorityModality(Modality) { return LOW_PRIORITY_MODALITIES.includes(Modality); diff --git a/platform/core/src/utils/makeCancelable.js b/platform/core/src/utils/makeCancelable.js index 11e507103ad..f64af596808 100644 --- a/platform/core/src/utils/makeCancelable.js +++ b/platform/core/src/utils/makeCancelable.js @@ -1,12 +1,16 @@ export default function makeCancelable(thenable) { let isCanceled = false; const promise = Promise.resolve(thenable).then( - function(result) { - if (isCanceled) throw Object.freeze({ isCanceled }); + function (result) { + if (isCanceled) { + throw Object.freeze({ isCanceled }); + } return result; }, - function(error) { - if (isCanceled) throw Object.freeze({ isCanceled, error }); + function (error) { + if (isCanceled) { + throw Object.freeze({ isCanceled, error }); + } throw error; } ); diff --git a/platform/core/src/utils/makeDeferred.js b/platform/core/src/utils/makeDeferred.js index 8becf3295f3..86a25ec17d6 100644 --- a/platform/core/src/utils/makeDeferred.js +++ b/platform/core/src/utils/makeDeferred.js @@ -1,7 +1,7 @@ export default function makeDeferred() { let reject, resolve, - promise = new Promise(function(res, rej) { + promise = new Promise(function (res, rej) { resolve = res; reject = rej; }); diff --git a/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js index 1975b3942f6..a3d5a7aeaf4 100644 --- a/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js +++ b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js @@ -11,15 +11,12 @@ * @returns Array view containing the palette data, or a promise to return one. * Returns undefined if the palette data is absent. */ -export default function fetchPaletteColorLookupTableData( - item, - tag, - descriptorTag -) { +export default function fetchPaletteColorLookupTableData(item, tag, descriptorTag) { const { PaletteColorLookupTableUID } = item; const paletteData = item[tag]; - if (paletteData === undefined && PaletteColorLookupTableUID === undefined) + if (paletteData === undefined && PaletteColorLookupTableUID === undefined) { return; + } // performance optimization - read UID and cache by UID return _getPaletteColor(item[tag], item[descriptorTag]); } @@ -28,7 +25,9 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { const numLutEntries = lutDescriptor[0]; const bits = lutDescriptor[2]; - if (!paletteColorLookupTableData) return undefined; + if (!paletteColorLookupTableData) { + return undefined; + } const arrayBufferToPaletteColorLUT = arraybuffer => { const lut = []; @@ -52,19 +51,12 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { if (paletteColorLookupTableData.InlineBinary) { try { - const arraybuffer = Uint8Array.from( - atob(paletteColorLookupTableData.InlineBinary), - c => c.charCodeAt(0) + const arraybuffer = Uint8Array.from(atob(paletteColorLookupTableData.InlineBinary), c => + c.charCodeAt(0) ); - return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT( - arraybuffer - )); + return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(arraybuffer)); } catch (e) { - console.log( - "Couldn't decode", - paletteColorLookupTableData.InlineBinary, - e - ); + console.log("Couldn't decode", paletteColorLookupTableData.InlineBinary, e); return undefined; } } @@ -72,12 +64,7 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { if (paletteColorLookupTableData.retrieveBulkData) { return paletteColorLookupTableData .retrieveBulkData() - .then( - val => - (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT( - val - )) - ); + .then(val => (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(val))); } console.error(`No data found for ${paletteColorLookupTableData} palette`); diff --git a/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js b/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js index edcd1cf894d..9e8425a1589 100644 --- a/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js +++ b/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js @@ -48,11 +48,7 @@ export default function getPixelSpacingInformation(instance) { type: TYPES.UNKNOWN, isProjection, }; - } else if ( - PixelSpacing && - ImagerPixelSpacing && - PixelSpacing === ImagerPixelSpacing - ) { + } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing === ImagerPixelSpacing) { // If Imager Pixel Spacing and Pixel Spacing are present and they have the same values, // then the user should be informed that the measurements are at the detector plane return { @@ -60,11 +56,7 @@ export default function getPixelSpacingInformation(instance) { type: TYPES.DETECTOR, isProjection, }; - } else if ( - PixelSpacing && - ImagerPixelSpacing && - PixelSpacing !== ImagerPixelSpacing - ) { + } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing !== ImagerPixelSpacing) { // If Imager Pixel Spacing and Pixel Spacing are present and they have different values, // then the user should be informed that these are "calibrated" // (in some unknown manner if Pixel Spacing Calibration Type and/or @@ -86,19 +78,19 @@ export default function getPixelSpacingInformation(instance) { pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor ); } else { - log.info( - 'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.' - ); + if (!instance._loggedSpacingMessage) { + log.info( + 'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.' + ); + instance._loggedSpacingMessage = true; + } } return { PixelSpacing: CorrectedImagerPixelSpacing, isProjection, }; - } else if ( - SequenceOfUltrasoundRegions && - typeof SequenceOfUltrasoundRegions === 'object' - ) { + } else if (SequenceOfUltrasoundRegions && typeof SequenceOfUltrasoundRegions === 'object') { const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions; const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10]; diff --git a/platform/core/src/utils/metadataProvider/unpackOverlay.js b/platform/core/src/utils/metadataProvider/unpackOverlay.js index 78e39b2c2b0..387687440bd 100644 --- a/platform/core/src/utils/metadataProvider/unpackOverlay.js +++ b/platform/core/src/utils/metadataProvider/unpackOverlay.js @@ -5,8 +5,7 @@ export default function unpackOverlay(arrayBuffer) { for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) { const bitIndex = byteIndex % 8; const bitByteIndex = Math.floor(byteIndex / 8); - byteArray[byteIndex] = - 1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex); + byteArray[byteIndex] = 1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex); } return byteArray; diff --git a/platform/core/src/utils/objectPath.js b/platform/core/src/utils/objectPath.js index 5727267d564..feae26db21e 100644 --- a/platform/core/src/utils/objectPath.js +++ b/platform/core/src/utils/objectPath.js @@ -6,7 +6,7 @@ export class ObjectPath { * @param path {String} A string representing the property to be set, e.g. "user.study.series.timepoint". * @param value {Any} The value of the property that will be set. * @return {Boolean} Returns "true" on success, "false" if any intermediate component of the supplied path - * ... is not a valid Object, in which case the property cannot be set. No excpetions are thrown. + * ... is not a valid Object, in which case the property cannot be set. No exceptions are thrown. */ static set(object, path, value) { let components = ObjectPath.getPathComponents(path), @@ -85,9 +85,7 @@ export class ObjectPath { * @return {Boolean} Returns "true" if the object is a real Object instance and "false" otherwise. */ static isValidObject(object) { - return ( - typeof object === 'object' && object !== null && object instanceof Object - ); + return typeof object === 'object' && object !== null && object instanceof Object; } static getPathComponents(path) { diff --git a/platform/core/src/utils/progressTrackingUtils.js b/platform/core/src/utils/progressTrackingUtils.js index efc3447d4a0..7ff2f826188 100644 --- a/platform/core/src/utils/progressTrackingUtils.js +++ b/platform/core/src/utils/progressTrackingUtils.js @@ -114,7 +114,7 @@ function finish(task) { /** * Generate a summarized snapshot of the current status of the given task List * @param {Object} list The List instance to be scanned - * @returns {Object} An obeject representing the summarized status of the list + * @returns {Object} An object representing the summarized status of the list */ function getOverallProgress(list) { const status = createStatus(); @@ -124,7 +124,9 @@ function getOverallProgress(list) { status.total++; if (isValidProgress(task.progress)) { status.partial += task.progress; - if (task.progress === 1.0 && task.failed) status.failures++; + if (task.progress === 1.0 && task.failed) { + status.failures++; + } } task = task.next; } @@ -147,10 +149,10 @@ function waitOn(list, thenable) { const task = increaseList(list); if (isTask(task)) { task.awaiting = Promise.resolve(thenable).then( - function() { + function () { finish(task); }, - function() { + function () { task.failed = true; finish(task); } @@ -221,14 +223,10 @@ function getTaskByName(list, name) { * @param {Object} list The List instance to which the observer will be appended * @param {Function} observer The observer (function) that will be executed * every time a change happens within the list - * @returns {boolean} Returns true on success and false otherewise + * @returns {boolean} Returns true on success and false otherwise */ function addObserver(list, observer) { - if ( - isList(list) && - Array.isArray(list.observers) && - typeof observer === 'function' - ) { + if (isList(list) && Array.isArray(list.observers) && typeof observer === 'function') { list.observers.push(observer); return true; } @@ -239,14 +237,10 @@ function addObserver(list, observer) { * Removes an observer (callback function) from a given List instance * @param {Object} list The instance List from which the observer will removed * @param {Function} observer The observer function to be removed - * @returns {boolean} Returns true on success and false otherewise + * @returns {boolean} Returns true on success and false otherwise */ function removeObserver(list, observer) { - if ( - isList(list) && - Array.isArray(list.observers) && - list.observers.length > 0 - ) { + if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) { const index = list.observers.indexOf(observer); if (index >= 0) { list.observers.splice(index, 1); @@ -274,9 +268,7 @@ function objectWithType(type, object) { } function isOfType(type, subject) { - return ( - subject !== null && typeof subject === 'object' && subject[TYPE] === type - ); + return subject !== null && typeof subject === 'object' && subject[TYPE] === type; } function isValidProgress(value) { @@ -297,12 +289,8 @@ function contains(list, task) { } function notify(list, data) { - if ( - isList(list) && - Array.isArray(list.observers) && - list.observers.length > 0 - ) { - list.observers.slice().forEach(function(observer) { + if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) { + list.observers.slice().forEach(function (observer) { if (typeof observer === 'function') { try { observer(data, list); diff --git a/platform/core/src/utils/progressTrackingUtils.test.js b/platform/core/src/utils/progressTrackingUtils.test.js index f9b098c584e..3a74c8c95f8 100644 --- a/platform/core/src/utils/progressTrackingUtils.test.js +++ b/platform/core/src/utils/progressTrackingUtils.test.js @@ -40,11 +40,7 @@ describe('progressTrackingUtils', () => { it('should call observer twice for each task', () => { const { list, observer } = context; - const promises = [ - Promise.resolve('A'), - Promise.resolve('B'), - Promise.resolve('C'), - ]; + const promises = [Promise.resolve('A'), Promise.resolve('B'), Promise.resolve('C')]; promises.forEach(promise => void utils.waitOn(list, promise)); return Promise.all(promises).then(() => { expect(observer).toBeCalledTimes(6); diff --git a/platform/core/src/utils/resolveObjectPath.js b/platform/core/src/utils/resolveObjectPath.js index d5e82babf56..c541732669c 100644 --- a/platform/core/src/utils/resolveObjectPath.js +++ b/platform/core/src/utils/resolveObjectPath.js @@ -10,8 +10,6 @@ export default function resolveObjectPath(root, path, defaultValue) { ); } value = root[path]; - return value === undefined && defaultValue !== undefined - ? defaultValue - : value; + return value === undefined && defaultValue !== undefined ? defaultValue : value; } } diff --git a/platform/core/src/utils/resolveObjectPath.test.js b/platform/core/src/utils/resolveObjectPath.test.js index 6d019a5bc37..838341fba2d 100644 --- a/platform/core/src/utils/resolveObjectPath.test.js +++ b/platform/core/src/utils/resolveObjectPath.test.js @@ -1,9 +1,9 @@ import resolveObjectPath from './resolveObjectPath'; -describe('resolveObjectPath', function() { +describe('resolveObjectPath', function () { let config; - beforeEach(function() { + beforeEach(function () { config = { active: { user: { @@ -21,13 +21,13 @@ describe('resolveObjectPath', function() { }; }); - it('should safely return deeply nested values from an object', function() { + it('should safely return deeply nested values from an object', function () { expect(resolveObjectPath(config, 'active.user.name.first')).toBe('John'); expect(resolveObjectPath(config, 'active.user.name.last')).toBe('Doe'); expect(resolveObjectPath(config, 'active.servers.0.ipv4')).toBe('10.0.0.1'); }); - it('should silently return undefined when intermediate values are not valid objects', function() { + it('should silently return undefined when intermediate values are not valid objects', function () { expect(resolveObjectPath(config, 'active.usr.name.first')).toBeUndefined(); expect(resolveObjectPath(config, 'active.name.last')).toBeUndefined(); expect(resolveObjectPath(config, 'active.servers.7.ipv4')).toBeUndefined(); diff --git a/platform/core/src/utils/roundNumber.js b/platform/core/src/utils/roundNumber.js index a441225da50..be76c7edea3 100644 --- a/platform/core/src/utils/roundNumber.js +++ b/platform/core/src/utils/roundNumber.js @@ -1,9 +1,34 @@ /** - * @param {string | number} value - * @param {number} decimals + * Truncates decimal points to that there is at least 1+precision significant + * digits. + * + * For example, with the default precision 2 (3 significant digits) + * * Values larger than 100 show no information after the decimal point + * * Values between 10 and 99 show 1 decimal point + * * Values between 1 and 9 show 2 decimal points + * + * @param value - to return a fixed measurement value from + * @param precision - defining how many digits after 1..9 are desired */ -function _round(value, decimals) { - return Number(value).toFixed(decimals); +function roundNumber(value: string | number, precision = 2): string { + if (value === undefined || value === null || value === '') return 'NaN'; + value = Number(value); + if (value < 0.0001) return `${value}`; + const fixedPrecision = + value >= 100 + ? precision - 2 + : value >= 10 + ? precision - 1 + : value >= 1 + ? precision + : value >= 0.1 + ? precision + 1 + : value >= 0.01 + ? precision + 2 + : value >= 0.001 + ? precision + 3 + : precision + 4; + return value.toFixed(fixedPrecision); } -export default _round; +export default roundNumber; diff --git a/platform/core/src/utils/sopClassDictionary.js b/platform/core/src/utils/sopClassDictionary.js index 5e95576958d..cb9712e910f 100644 --- a/platform/core/src/utils/sopClassDictionary.js +++ b/platform/core/src/utils/sopClassDictionary.js @@ -3,14 +3,10 @@ export const sopClassDictionary = { ComputedRadiographyImageStorage: '1.2.840.10008.5.1.4.1.1.1', DigitalXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.1', DigitalXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.1.1', - DigitalMammographyXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.1.2', - DigitalMammographyXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.1.2.1', - DigitalIntraOralXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.1.3', - DigitalIntraOralXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.1.3.1', + DigitalMammographyXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.2', + DigitalMammographyXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.2.1', + DigitalIntraOralXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.3', + DigitalIntraOralXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.3.1', CTImageStorage: '1.2.840.10008.5.1.4.1.1.2', EnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.1', LegacyConvertedEnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.2', @@ -23,14 +19,10 @@ export const sopClassDictionary = { UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1', EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2', SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7', - MultiframeSingleBitSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.1', - MultiframeGrayscaleByteSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.2', - MultiframeGrayscaleWordSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.3', - MultiframeTrueColorSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.4', + MultiframeSingleBitSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.1', + MultiframeGrayscaleByteSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.2', + MultiframeGrayscaleWordSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.3', + MultiframeTrueColorSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.4', Sop12LeadECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.1', GeneralECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.2', AmbulatoryECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.3', @@ -44,8 +36,7 @@ export const sopClassDictionary = { ColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.2', PseudoColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.3', BlendingSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.4', - XAXRFGrayscaleSoftcopyPresentationStateStorage: - '1.2.840.10008.5.1.4.1.1.11.5', + XAXRFGrayscaleSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.5', XRayAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.12.1', EnhancedXAImageStorage: '1.2.840.10008.5.1.4.1.1.12.1.1', XRayRadiofluoroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.12.2', @@ -53,14 +44,11 @@ export const sopClassDictionary = { XRay3DAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.1', XRay3DCraniofacialImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.2', BreastTomosynthesisImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.3', - BreastProjectionXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.13.1.4', - BreastProjectionXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.13.1.5', + BreastProjectionXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.13.1.4', + BreastProjectionXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.13.1.5', IntravascularOpticalCoherenceTomographyImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.14.1', - IntravascularOpticalCoherenceTomographyImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.14.2', + IntravascularOpticalCoherenceTomographyImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.14.2', NuclearMedicineImageStorage: '1.2.840.10008.5.1.4.1.1.20', RawDataStorage: '1.2.840.10008.5.1.4.1.1.66', SpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.1', @@ -92,8 +80,7 @@ export const sopClassDictionary = { OphthalmicAxialMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.7', IntraocularLensCalculationsStorage: '1.2.840.10008.5.1.4.1.1.78.8', MacularGridThicknessandVolumeReport: '1.2.840.10008.5.1.4.1.1.79.1', - OphthalmicVisualFieldStaticPerimetryMeasurementsStorage: - '1.2.840.10008.5.1.4.1.1.80.1', + OphthalmicVisualFieldStaticPerimetryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.80.1', OphthalmicThicknessMapStorage: '1.2.840.10008.5.1.4.1.1.81.1', CornealTopographyMapStorage: '1.2.840.10008.5.1.4.1.1.82.1', BasicTextSR: '1.2.840.10008.5.1.4.1.1.88.11', diff --git a/platform/core/src/utils/sortBy.js b/platform/core/src/utils/sortBy.js index 39f21ef245b..8809b0fbd15 100644 --- a/platform/core/src/utils/sortBy.js +++ b/platform/core/src/utils/sortBy.js @@ -3,7 +3,7 @@ export default function sortBy() { var fields = [].slice.call(arguments), n_fields = fields.length; - return function(A, B) { + return function (A, B) { var a, b, field, key, reverse, result, i; for (i = 0; i < n_fields; i++) { diff --git a/platform/core/src/utils/sortInstancesByPosition.ts b/platform/core/src/utils/sortInstancesByPosition.ts index a441796fbe4..4fd248354f6 100644 --- a/platform/core/src/utils/sortInstancesByPosition.ts +++ b/platform/core/src/utils/sortInstancesByPosition.ts @@ -15,10 +15,8 @@ export default function sortInstances(instances: Array) { return instances; } - const { - ImagePositionPatient: referenceImagePositionPatient, - ImageOrientationPatient, - } = instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image + const { ImagePositionPatient: referenceImagePositionPatient, ImageOrientationPatient } = + instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image if (!referenceImagePositionPatient || !ImageOrientationPatient) { return instances; @@ -49,11 +47,7 @@ export default function sortInstances(instances: Array) { const positionVector = vec3.create(); - vec3.sub( - positionVector, - referenceImagePositionPatient, - imagePositionPatient - ); + vec3.sub(positionVector, referenceImagePositionPatient, imagePositionPatient); const distance = vec3.dot(positionVector, scanAxisNormal); diff --git a/platform/core/src/utils/sortStudy.ts b/platform/core/src/utils/sortStudy.ts index a39c8a16a63..8928a2f348d 100644 --- a/platform/core/src/utils/sortStudy.ts +++ b/platform/core/src/utils/sortStudy.ts @@ -1,19 +1,17 @@ import isLowPriorityModality from './isLowPriorityModality'; const compareSeriesDateTime = (a, b) => { - const seriesDateA = Date.parse( - `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` - ); - const seriesDateB = Date.parse( - `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` - ); + const seriesDateA = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`); + const seriesDateB = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`); return seriesDateA - seriesDateB; }; const defaultSeriesSort = (a, b) => { const seriesNumberA = a.SeriesNumber ?? a.seriesNumber; const seriesNumberB = b.SeriesNumber ?? b.seriesNumber; - if (seriesNumberA === seriesNumberB) return compareSeriesDateTime(a, b); + if (seriesNumberA === seriesNumberB) { + return compareSeriesDateTime(a, b); + } return seriesNumberA - seriesNumberB; }; @@ -24,12 +22,8 @@ const defaultSeriesSort = (a, b) => { * @param {Object} secondSeries */ function seriesInfoSortingCriteria(firstSeries, secondSeries) { - const aLowPriority = isLowPriorityModality( - firstSeries.Modality ?? firstSeries.modality - ); - const bLowPriority = isLowPriorityModality( - secondSeries.Modality ?? secondSeries.modality - ); + const aLowPriority = isLowPriorityModality(firstSeries.Modality ?? firstSeries.modality); + const bLowPriority = isLowPriorityModality(secondSeries.Modality ?? secondSeries.modality); if (aLowPriority) { return bLowPriority ? defaultSeriesSort(secondSeries, firstSeries) : 1; @@ -67,8 +61,11 @@ const sortStudySeries = ( seriesSortingCriteria = seriesSortCriteria.default, sortFunction = null ) => { - if (typeof sortFunction === 'function') return sortFunction(series); - else return series.sort(seriesSortingCriteria); + if (typeof sortFunction === 'function') { + return sortFunction(series); + } else { + return series.sort(seriesSortingCriteria); + } }; /** @@ -117,10 +114,4 @@ export default function sortStudy( return study; } -export { - sortStudy, - sortStudySeries, - sortStudyInstances, - sortingCriteria, - seriesSortCriteria, -}; +export { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria, seriesSortCriteria }; diff --git a/platform/core/src/utils/splitComma.ts b/platform/core/src/utils/splitComma.ts index 4354e0ce0bf..922a944e1bd 100644 --- a/platform/core/src/utils/splitComma.ts +++ b/platform/core/src/utils/splitComma.ts @@ -1,6 +1,8 @@ -/** Splits a list of stirngs by commas within the strings */ +/** Splits a list of strings by commas within the strings */ const splitComma = (strings: string[]): string[] => { - if (!strings) return null; + if (!strings) { + return null; + } for (let i = 0; i < strings.length; i++) { const comma = strings[i].indexOf(','); if (comma !== -1) { @@ -21,10 +23,10 @@ const getSplitParam = ( lowerCaseKey: string, params = new URLSearchParams(window.location.search) ): string[] => { - const sourceKey = [...params.keys()].find( - it => it.toLowerCase() === lowerCaseKey - ); - if (!sourceKey) return; + const sourceKey = [...params.keys()].find(it => it.toLowerCase() === lowerCaseKey); + if (!sourceKey) { + return; + } return splitComma(params.getAll(sourceKey)); }; diff --git a/platform/core/src/utils/subscribeToNextViewportGridChange.ts b/platform/core/src/utils/subscribeToNextViewportGridChange.ts index e6c72d04856..6ffbc4a1c64 100644 --- a/platform/core/src/utils/subscribeToNextViewportGridChange.ts +++ b/platform/core/src/utils/subscribeToNextViewportGridChange.ts @@ -1,7 +1,7 @@ import { ViewportGridService } from '../services'; /** - * Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that + * Subscribes to the very next LAYOUT_CHANGED event that * is not currently on the event queue. The subscriptions are made on a 'zero' * timeout so as to avoid responding to any of those events currently on the event queue. * The subscription persists only for a single invocation of either event. @@ -20,14 +20,7 @@ function subscribeToNextViewportGridChange( }; const subscriptions = [ - viewportGridService.subscribe( - viewportGridService.EVENTS.LAYOUT_CHANGED, - callback - ), - viewportGridService.subscribe( - viewportGridService.EVENTS.GRID_STATE_CHANGED, - callback - ), + viewportGridService.subscribe(viewportGridService.EVENTS.LAYOUT_CHANGED, callback), ]; }; diff --git a/platform/docs/CHANGELOG.md b/platform/docs/CHANGELOG.md new file mode 100644 index 00000000000..145a2dcb26a --- /dev/null +++ b/platform/docs/CHANGELOG.md @@ -0,0 +1,452 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + + +### Features + +* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89)) + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + + +### Bug Fixes + +* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5)) + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + + +### Bug Fixes + +* **cli:** various fixes for adding custom modes and extensions ([#3683](https://github.com/OHIF/Viewers/issues/3683)) ([dc73b18](https://github.com/OHIF/Viewers/commit/dc73b187484da029a2664bb1302f30137c973b8c)) + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package ohif-docs diff --git a/platform/docs/docs/README.md b/platform/docs/docs/README.md index 05896daea4c..3f6446dd13c 100644 --- a/platform/docs/docs/README.md +++ b/platform/docs/docs/README.md @@ -18,12 +18,11 @@ Key features: Offers a Data Source API for communicating with archives over proprietary API formats. - Provides a plugin framework for creating task-based workflow modes which can - re-use core functionality. + reuse core functionality. - Beautiful user interface (UI) designed with extensibility in mind. UI components available in a reusable component library built with React.js and Tailwind CSS -![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.jpg)
@@ -32,6 +31,18 @@ Key features:
+
+
+ + + +| | | | +| :-: | :--- | :--- | +| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | +| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | +| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | +| Microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) | +| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | diff --git a/platform/docs/docs/assets/img/browser-console-non-secure-context.png b/platform/docs/docs/assets/img/browser-console-non-secure-context.png new file mode 100644 index 00000000000..3fb4f1bcdcd Binary files /dev/null and b/platform/docs/docs/assets/img/browser-console-non-secure-context.png differ diff --git a/platform/docs/docs/assets/img/cors-browser-console-errors.png b/platform/docs/docs/assets/img/cors-browser-console-errors.png new file mode 100644 index 00000000000..0b8d062ca7d Binary files /dev/null and b/platform/docs/docs/assets/img/cors-browser-console-errors.png differ diff --git a/platform/docs/docs/assets/img/cors-network-panel-errors.png b/platform/docs/docs/assets/img/cors-network-panel-errors.png new file mode 100644 index 00000000000..3820b764312 Binary files /dev/null and b/platform/docs/docs/assets/img/cors-network-panel-errors.png differ diff --git a/platform/docs/docs/assets/img/data-source-configuration-ui.png b/platform/docs/docs/assets/img/data-source-configuration-ui.png new file mode 100644 index 00000000000..f04956e0752 Binary files /dev/null and b/platform/docs/docs/assets/img/data-source-configuration-ui.png differ diff --git a/platform/docs/docs/assets/img/OHIF-Viewer.jpg b/platform/docs/docs/assets/img/demo-measurements.jpg similarity index 100% rename from platform/docs/docs/assets/img/OHIF-Viewer.jpg rename to platform/docs/docs/assets/img/demo-measurements.jpg diff --git a/platform/docs/docs/assets/img/demo-microscopy.png b/platform/docs/docs/assets/img/demo-microscopy.png new file mode 100644 index 00000000000..ad548012610 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-microscopy.png differ diff --git a/platform/docs/docs/assets/img/demo-ptct.png b/platform/docs/docs/assets/img/demo-ptct.png new file mode 100644 index 00000000000..83472f0766e Binary files /dev/null and b/platform/docs/docs/assets/img/demo-ptct.png differ diff --git a/platform/docs/docs/assets/img/demo-segmentation.png b/platform/docs/docs/assets/img/demo-segmentation.png new file mode 100644 index 00000000000..3482a15c515 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-segmentation.png differ diff --git a/platform/docs/docs/assets/img/demo-volumeRendering.png b/platform/docs/docs/assets/img/demo-volumeRendering.png new file mode 100644 index 00000000000..4c508ab6d36 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-volumeRendering.png differ diff --git a/platform/docs/docs/assets/img/google-create-credentials.png b/platform/docs/docs/assets/img/google-create-credentials.png new file mode 100644 index 00000000000..e6534fe0806 Binary files /dev/null and b/platform/docs/docs/assets/img/google-create-credentials.png differ diff --git a/platform/docs/docs/assets/img/google-enable-apis.png b/platform/docs/docs/assets/img/google-enable-apis.png new file mode 100644 index 00000000000..434bf77771e Binary files /dev/null and b/platform/docs/docs/assets/img/google-enable-apis.png differ diff --git a/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png b/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png new file mode 100644 index 00000000000..c98ddca97c0 Binary files /dev/null and b/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png differ diff --git a/platform/docs/docs/assets/img/google-manually-add-scopes.png b/platform/docs/docs/assets/img/google-manually-add-scopes.png new file mode 100644 index 00000000000..9500b8557dd Binary files /dev/null and b/platform/docs/docs/assets/img/google-manually-add-scopes.png differ diff --git a/platform/docs/docs/assets/img/google-oauth-consent-steps.png b/platform/docs/docs/assets/img/google-oauth-consent-steps.png new file mode 100644 index 00000000000..67d4a42ad71 Binary files /dev/null and b/platform/docs/docs/assets/img/google-oauth-consent-steps.png differ diff --git a/platform/docs/docs/assets/img/google-projects-drop-down.png b/platform/docs/docs/assets/img/google-projects-drop-down.png new file mode 100644 index 00000000000..fb203105fc2 Binary files /dev/null and b/platform/docs/docs/assets/img/google-projects-drop-down.png differ diff --git a/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png b/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png new file mode 100644 index 00000000000..e129854517e Binary files /dev/null and b/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png differ diff --git a/platform/docs/docs/assets/img/iframe-basic.png b/platform/docs/docs/assets/img/iframe-basic.png new file mode 100644 index 00000000000..25ea20708bf Binary files /dev/null and b/platform/docs/docs/assets/img/iframe-basic.png differ diff --git a/platform/docs/docs/assets/img/iframe-headers.png b/platform/docs/docs/assets/img/iframe-headers.png new file mode 100644 index 00000000000..27d649d22f3 Binary files /dev/null and b/platform/docs/docs/assets/img/iframe-headers.png differ diff --git a/platform/docs/docs/assets/img/ohif-non-secure-context.png b/platform/docs/docs/assets/img/ohif-non-secure-context.png new file mode 100644 index 00000000000..b4ff6d0d350 Binary files /dev/null and b/platform/docs/docs/assets/img/ohif-non-secure-context.png differ diff --git a/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png b/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png new file mode 100644 index 00000000000..6f0c98b04e4 Binary files /dev/null and b/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png differ diff --git a/platform/docs/docs/assets/img/self-signed-cert-warning.png b/platform/docs/docs/assets/img/self-signed-cert-warning.png new file mode 100644 index 00000000000..41df65f914b Binary files /dev/null and b/platform/docs/docs/assets/img/self-signed-cert-warning.png differ diff --git a/platform/docs/docs/configuration/configurationFiles.md b/platform/docs/docs/configuration/configurationFiles.md index b195e46f4cf..9141cc108e5 100644 --- a/platform/docs/docs/configuration/configurationFiles.md +++ b/platform/docs/docs/configuration/configurationFiles.md @@ -10,10 +10,11 @@ After following the steps outlined in OHIF Viewer has data for several studies and their images. You didn't add this data, so where is it coming from? -By default, the viewer is configured to connect to a remote server hosted by the -nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, -the time may come when you want to develop using your own data either locally or -remotely. +By default, the viewer is configured to connect to a Amazon S3 bucket that is hosting +a Static WADO server (see [Static WADO DICOMWeb](https://github.com/RadicalImaging/static-dicomweb)). +By default we use `default.js` for the configuration file. You can change this by setting the `APP_CONFIG` environment variable +and select other options such as `config/local_orthanc.js` or `config/google.js`. + ## Configuration Files @@ -33,10 +34,10 @@ window.config = { showStudyList: true, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -48,6 +49,7 @@ window.config = { enableStudyLazyLoad: true, supportsFuzzyMatching: true, supportsWildcard: true, + omitQuotationForMultipartRequest: true, }, }, ], @@ -82,10 +84,10 @@ window.config = ({ servicesManager } = {}) => { routerBasename: '/', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -97,6 +99,7 @@ window.config = ({ servicesManager } = {}) => { enableStudyLazyLoad: true, supportsFuzzyMatching: true, supportsWildcard: true, + omitQuotationForMultipartRequest: true, }, }, ], @@ -105,21 +108,86 @@ window.config = ({ servicesManager } = {}) => { }; ``` + + + + ## Configuration Options -Here are a list of some options available: +Here are a list of some options available: +- `disableEditing`: If true, it disables editing in OHIF, hiding edit buttons in segmentation + panel and locking already stored measurements. - `maxNumberOfWebWorkers`: The maximum number of web workers to use for decoding. Defaults to minimum of `navigator.hardwareConcurrency` and what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values. +- `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ] +- `requestTransferSyntaxUID` : Request a specific Transfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set) - `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that if auth headers are used, a preflight request is required. - `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type. +- `modesConfiguration`: Allows overriding modes configuration. + - Example config: + ```js + modesConfiguration: { + '@ohif/mode-longitudinal': { + displayName: 'Custom Name', + routeName: 'customRouteName', + routes: [ + { + path: 'customPath', + layoutTemplate: () => { + /** Custom Layout */ + return { + id: ohif.layout, + props: { + leftPanels: [tracked.thumbnailList], + rightPanels: [dicomSeg.panel, tracked.measurements], + rightPanelDefaultClosed: true, + viewports: [ + { + namespace: tracked.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + } + }, + ``` + Note: Although the mode configuration is passed to the mode factory function, it is up to the particular mode itself if its going to use it to allow overwriting its original configuration e.g. + ```js + function modeFactory({ modeConfiguration }) { + return { + id, + routeName: 'viewer', + displayName: 'Basic Viewer', + ... + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + ... + }, + /** + * This mode allows its configuration to be overwritten by + * destructuring the modeConfiguration value from the mode fatory function + * at the end of the mode configuration definition. + */ + ...modeConfiguration, + }; + } + ``` - `showLoadingIndicator`: (default to true), if set to false, the loading indicator will not be shown when navigating between studies. +- `use16BitDataType`: (default to false), if set to true, it will use 16 bit data type for the image data wherever possible which has + significant impact on reducing the memory usage. However, the 16Bit textures require EXT_texture_norm16 extension in webGL 2.0 (you can check if you have it here https://webglreport.com/?v=2). In addition to the extension, there are reported problems for Intel Macs that might cause the viewer to crash. In summary, it is great a configuration if you have support for it. +- `useSharedArrayBuffer` (default to 'TRUE', options: 'AUTO', 'FALSE', 'TRUE', note that these are strings), for volume loading we use sharedArrayBuffer to be able to + load the volume progressively as the data arrives (each webworker has the shared buffer and can write to it). However, there might be certain environments that do not support sharedArrayBuffer. In that case, you can set this flag to false and the viewer will use the regular arrayBuffer which might be slower for large volume loading. +- `supportsWildcard`: (default to false), if set to true, the datasource will support wildcard matching for patient name and patient id. - `dangerouslyUseDynamicConfig`: Dynamic config allows user to pass `configUrl` query string. This allows to load config without recompiling application. If the `configUrl` query string is passed, the worklist and modes will load from the referenced json rather than the default .env config. If there is no `configUrl` path provided, the default behaviour is used and there should not be any deviation from current user experience.
Points to consider while using `dangerouslyUseDynamicConfig`:
- User have to enable this feature by setting `dangerouslyUseDynamicConfig.enabled:true`. By default it is `false`. - - Regex helps to avoid easy exploit. Dafault is `/.*/`. Setup your own regex to choose a specific source of configuration only. + - Regex helps to avoid easy exploit. Default is `/.*/`. Setup your own regex to choose a specific source of configuration only. - System administrators can return `cross-origin: same-origin` with OHIF files to disallow any loading from other origin. It will block read access to resources loaded from a different origin to avoid potential attack vector. - Example config: ```js @@ -134,7 +202,38 @@ Example 2, to restricts to either hosptial.com or othersite.com.
`regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/`
Example usage:
`http://localhost:3000/?configUrl=http://localhost:3000/config/example.json`
- +- `onConfiguration`: Currently only available for DicomWebDataSource, this option allows the interception of the data source configuration for dynamic values e.g. values coming from url params or query params. Here is an example of building the dicomweb datasource configuration object with values that are based on the route url params: + ``` + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'gcpdicomweb', + configuration: { + friendlyName: 'GCP DICOMWeb Server', + name: 'gcpdicomweb', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: false, + singlepart: 'bulkdata,video,pdf', + useBulkDataURI: false, + onConfiguration: (dicomWebConfig, options) => { + const { params } = options; + const { project, location, dataset, dicomStore } = params; + const pathUrl = `https://healthcare.googleapis.com/v1/projects/${project}/locations/${location}/datasets/${dataset}/dicomStores/${dicomStore}/dicomWeb`; + return { + ...dicomWebConfig, + wadoRoot: pathUrl, + qidoRoot: pathUrl, + wadoUri: pathUrl, + wadoUriRoot: pathUrl, + }; + }, + }, + }, + ``` +This configuration would allow the user to build a dicomweb configuration from a GCP healthcare api path e.g. http://localhost:3000/projects/your-gcp-project/locations/us-central1/datasets/your-dataset/dicomStores/your-dicom-store/study/1.3.6.1.4.1.1234.5.2.1.1234.1234.123123123123123123123123123123 [dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light [dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker diff --git a/platform/docs/docs/configuration/dataSources/configuration-ui.md b/platform/docs/docs/configuration/dataSources/configuration-ui.md new file mode 100644 index 00000000000..b4279a26a2b --- /dev/null +++ b/platform/docs/docs/configuration/dataSources/configuration-ui.md @@ -0,0 +1,177 @@ +--- +sidebar_position: 6 +sidebar_label: Configuration UI +--- + +# Configuration UI + +OHIF provides for a generic mechanism for configuring a data source. This is +most useful for those organizations with several data sources +that share common (path) hierarchies. For example, an organization may have several DICOM stores +in the Google Cloud Healthcare realm where each is organized into various projects, +location, data sets and DICOM stores. + +By implementing the `BaseDataSourceConfigurationAPI` and +`BaseDataSourceConfigurationAPIItem` in an [OHIF extension](../../platform/extensions/index.md), a data source can +be made configurable via the generic UI as is depicted below for a +Google Cloud Healthcare data source. + +![Data source configuration UI](../../assets/img/data-source-configuration-ui.png) + +:::tip +A datasource root URI can be [fully or partially specified](../../deployment/google-cloud-healthcare.md#configuring-google-cloud-healthcare-as-a-datasource-in-ohif) +in the OHIF configuration file. +::: + +## `BaseDataSourceConfigurationAPIItem` interface + +Each (path) item of a data source is represented by an instance of this interface. +At the very least each of these items must expose two properties: + +|Property |Description| +|---------|-----------| +|id|a string that uniquely identifies the item| +|name|a human readable name for the item| + +Note that information such as where in the path hierarchy the item exists +has been omitted, but can be added in any concrete class that might implement this +interface. For example, the the Google Cloud Healthcare implementation of this +interface (`GoogleCloudDataSourceConfigurationAPIItem`) adds an `itemType` +(i.e. projects, locations, datasets, or dicomStores) and `url`. + +## `BaseDataSourceConfigurationAPI` interface + +The implementation of this interface is at the heart of the configuration process. +It possesses several methods for building up a data source path based on various +`BaseDataSourceConfigurationAPIItem` objects that are set via calls to the `setCurrentItem` +method. + +The constructor for the concrete class implementation should accept whatever +parameters are necessary for configuring the data source. One argument +to the constructor must be the string identifying the name of the data source +to be configured. Furthermore, considering that the `ExtensionManager` possesses +API to configure and update data sources, it too will likely be an argument to +the constructor. See [Creation via Customization Module](#creation-via-customization-module) +for more information on how the constructor is invoked via a factory method. + +For an example implementation of this interface see `GoogleCloudDataSourceConfigurationAPI`. + +### Interface Methods + +Each of the following subsections lists a method of the interface with a description +detailing what the method should do. + +#### `getItemLabels` + +Gets the i18n labels (i.e. the i18n lookup keys) for each of the configurable items +of the data source configuration API. For example, for the Google Cloud Healthcare +API, this would be `['Project', 'Location', 'Data set', 'DICOM store']`. + +Besides the configurable item labels themselves, several other string look ups +are used base on EACH of the labels returned by this method. +For instance, for the label `{itemLabel}``, the following strings are fetched for +translation... +1. `No {itemLabel} available` + - used to indicate no such items are available + - for example, for Google, `No Project available` would be 'No projects available' +2. `Select {itemLabel}` + - used to direct selection of the item + - for example, for Google, `Select Project` would be 'Select a project' +3. `Error fetching {itemLabel} list` + - used to indicate an error occurred fetching the list of items + - usually accompanied by the error itself + - for example, for Google, `Error fetching Project list` would be 'Error fetching projects' +4. `Search {itemLabel} list` + - used as the placeholder text for filtering a list of items + - for example, for Google, `Search Project list` would be 'Search projects' + +#### `initialize` + +Initializes the cloud server API and returns the top-level sub-items +that can be chosen to begin the process of configuring a data source. +For example, for the Google Cloud Healthcare API, this would perform the initial request +to fetch the top level projects for the logged in user account. + +#### `setCurrentItem` + +Sets the current path item that is passed as an argument to the method and +returns the sub-items of that item +that can be further chosen to configure a data source. +When setting the last configurable item of the data source (path), this method +returns an empty list AND configures the active data source with the selected +items path. + +For example, for the Google Cloud Healthcare API, this would take the current item +(say a data set) and queries and returns its sub-items (i.e. all of the DICOM stores +contained in that data set). Furthermore, whenever the item to set is a DICOM store, +the Google Cloud Healthcare API implementation would update the OHIF data source +associated with this instance to point to that DICOM store. + +#### `getConfiguredItems` + +Gets the list of items currently configured for the data source associated with +this API instance. The resultant array must be the same length as the result of +`getItemLabels`. Furthermore the items returned should correspond (index-wise) +with the labels returned from `getItemLabels`. + +## Creation via Customization Module + +The generic UI (i.e. `DataSourceConfigurationComponent`) uses the +[OHIF UI customization service](../../platform/services/ui/customization-service.md) to +instantiate the `BaseDataSourceConfigurationAPI` instance to configure a data source. + +A UI configurable data source should have a `configurationAPI` field as part of +its `configuration` in the OHIF config file. The `configurationAPI` value is the +customization id of the customization module that provides the factory method +to instantiate the `BaseDataSourceConfigurationAPI` instance. + +For example, the following is a snippet of a Google Cloud Healthcare data source configuration. + +```js + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'google-dicomweb', + configuration: { + name: 'GCP', + wadoUriRoot: 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/...', + ... + configurationAPI: 'ohif.dataSourceConfigurationAPI.google', + ... + }, + }, + ] +``` + +This suggests that the factory method is provided by the `'ohif.dataSourceConfigurationAPI.google'` +customization module. That customization module is provided by the `default` extension's +`getCustomizationModule` and looks something like the following snippet of code. Notice that +the factory method's name MUST be `factory` and accept one argument - the data source name. +Furthermore note how the constructor is invoked with anything required by the concrete configuration +API class. + +```js +export default function getCustomizationModule({ + servicesManager, + extensionManager, +}) { + return [ + { + name: 'default', + value: [ + { + // The factory for creating an instance of a BaseDataSourceConfigurationAPI for Google Cloud Healthcare + id: 'ohif.dataSourceConfigurationAPI.google', + factory: (dataSourceName: string) => + new GoogleCloudDataSourceConfigurationAPI( + dataSourceName, + servicesManager, + extensionManager + ), + }, + ], + }, + ]; +} + +``` diff --git a/platform/docs/docs/configuration/dataSources/dicom-json.md b/platform/docs/docs/configuration/dataSources/dicom-json.md index 08cbac7522f..20b0036d057 100644 --- a/platform/docs/docs/configuration/dataSources/dicom-json.md +++ b/platform/docs/docs/configuration/dataSources/dicom-json.md @@ -153,3 +153,14 @@ directly from Your public folder should look like this: ![](../../assets/img/dicom-json-public.png) + + +:::note +When hosting the DICOM JSON files, it is important to be aware that certain providers +do not automatically handle the 404 error and fallback to index.html. For example, Netlify +handles this, but Azure does not. Consequently, when you attempt to access a link with a +specific URL, a 404 error will be displayed. + +This issue also occurs locally, where the http-server does not handle it. However, +if you utilize the `serve` package (npx serve ./dist -l 8080 -s), it effectively addresses this problem. +::: diff --git a/platform/docs/docs/configuration/dataSources/dicom-web.md b/platform/docs/docs/configuration/dataSources/dicom-web.md index dcf4749ca80..5548d3c39df 100644 --- a/platform/docs/docs/configuration/dataSources/dicom-web.md +++ b/platform/docs/docs/configuration/dataSources/dicom-web.md @@ -74,7 +74,7 @@ _Upload your first Study:_ #### Orthanc: Learn More You can see the `docker-compose.yml` file this command runs at -[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on +[`/platform/app/.recipes/Nginx-Orthanc`][orthanc-docker-compose], and more on Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. #### Connecting to Orthanc @@ -131,10 +131,10 @@ window.config = { showStudyList: true, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -160,6 +160,13 @@ The following properties can be added to the `configuration` property of each da ##### `dicomUploadEnabled` A boolean indicating if the DICOM upload to the data source is permitted/accepted or not. A value of true provides a link on the OHIF work list page that allows for DICOM files from the local file system to be uploaded to the data source +:::tip +The [OHIF plugin for Orthanc](https://book.orthanc-server.com/plugins/ohif.html) by default utilizes the DICOM JSON data +source and it has been discovered that only those studies uploaded to Orthanc AFTER the plugin has been installed are +available as DICOM JSON. As such, if the OHIF plugin for Orthanc is desired for studies uploaded prior to installing the plugin, +then consider switching to using [DICOMweb instead](https://book.orthanc-server.com/plugins/ohif.html#using-dicomweb). +::: + ![toolbarModule-layout](../../assets/img/uploader.gif) #### `singlepart` @@ -212,7 +219,7 @@ An overview of steps for running OHIF Viewer using a local DCM4CHEE is shown below:
- +
[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light @@ -230,3 +237,5 @@ below: https://github.com/OHIF/Viewers/tree/master/platform/app/public/html-templates [config-files]: https://github.com/OHIF/Viewers/tree/master/platform/app/public/config +[storescu]: http://support.dcmtk.org/docs/storescu.html +[webpack-proxy]: https://webpack.js.org/configuration/dev-server/#devserverproxy diff --git a/platform/docs/docs/configuration/dataSources/static-files.md b/platform/docs/docs/configuration/dataSources/static-files.md index 296a879965a..aa69d4bbafb 100644 --- a/platform/docs/docs/configuration/dataSources/static-files.md +++ b/platform/docs/docs/configuration/dataSources/static-files.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 sidebar_label: Static Files --- @@ -18,13 +18,15 @@ It can be compiled with Java and Gradle, and then run against a set of dicom, in the example located in /dicom/study1 outputting to /dicomweb, and then a server run against that data, like this: -``` +```bash git clone https://github.com/OHIF/static-wado cd static-wado ./gradlew installDist StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 cd /dicomweb npx http-server -p 5000 --cors -g + +# you can use npx serve ./dist -l 8080 -s as an alternative to http-server ``` There is then a dev environment in the platform/app directory which can be diff --git a/platform/docs/docs/configuration/url.md b/platform/docs/docs/configuration/url.md index 444146a198a..fc8eaa2e947 100644 --- a/platform/docs/docs/configuration/url.md +++ b/platform/docs/docs/configuration/url.md @@ -59,9 +59,11 @@ If you happen to have multiple data sources configured, you can filter the WorkList by adding the `dataSources` query parameter. ```js -/?dataSourcename=orthanc +/?dataSources=orthanc ``` +Note: you should pass the `sourceName` of the data source in the configuration file (not the friendly name nor the name) + :::tip You can add `sortBy` and `sortDirection` query parameters to sort the WorkList @@ -109,7 +111,7 @@ You can open more than one study in the Viewer by adding the `StudyInstanceUIDs` :::tip -You can ues this feature to open a current and prior study in the Viewer. +You can use this feature to open a current and prior study in the Viewer. Read more in the [Hanging Protocol Module](../platform/extensions/modules/hpModule.md#matching-on-prior-study-with-uid) section. You can also use commas to separate values. diff --git a/platform/docs/docs/deployment/authorization.md b/platform/docs/docs/deployment/authorization.md index daba7454e7b..281b8f91bc8 100644 --- a/platform/docs/docs/deployment/authorization.md +++ b/platform/docs/docs/deployment/authorization.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 5 sidebar_label: Authorization --- diff --git a/platform/docs/docs/deployment/build-for-production.md b/platform/docs/docs/deployment/build-for-production.md index b1fa6999ccc..7da884abdd5 100644 --- a/platform/docs/docs/deployment/build-for-production.md +++ b/platform/docs/docs/deployment/build-for-production.md @@ -70,7 +70,7 @@ and registered extension's features, are configured using this file. The easiest way to apply your own configuration is to modify the `default.js` file. For more advanced configuration options, check out our -[configuration essentials guide](../configuration/index.md). +[configuration essentials guide](../configuration/configurationFiles.md). ## Next Steps @@ -106,12 +106,38 @@ yarn global add http-server # Serve the files in our current directory # Accessible at: `http://localhost:8080` npx http-server ./dist + +# you can use npx serve ./dist -l 8080 -s as an alternative to http-server ``` +:::caution +In the video below notice that there is `platform/viewer` which has been renamed to `platform/app` in the latest version +::: +
+### Build for non-root path + +If you would like to access the viewer from a non-root path (e.g., `/my-awesome-viewer` instead of `/`), +You can achieve so by using the `PUBLIC_URL` environment variable AND the `routerBasename` configuration option. + +1. use a config (e.g. config/myConfig.js) file that is using the `routerBasename` of your choice `/my-awesome-viewer` (note there is only one / - it is not /my-awesome-viewer/). +2. build the viewer with `PUBLIC_URL=/my-awesome-viewer/ APP_CONFIG=config/myConfig.js yarn build` (note there are two / - it is not /my-awesome-viewer). + + +:::tip +The PUBLIC_URL tells the application where to find the static assets and the routerBasename will tell the application how to handle the routes +::: + +:::tip +Testing, you can use `npx http-server` to serve the files in the generated `dist` folder and access the viewer from `http://localhost:8080/my-awesome-viewer`. To achieve +so, you should first rename the `dist` folder to `my-awesome-viewer` and then change the working directory +to the `platform/app` folder and run `npx http-server ./`. Then on the browser, you can access the viewer from `http://localhost:8080/my-awesome-viewer` +::: + + ### Automating Builds and Deployments If you found setting up your environment and running all of these steps to be a @@ -127,6 +153,6 @@ web application. For a starting point, check out this repository's own use of: [circleci]: https://circleci.com/gh/OHIF/Viewers [circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml [netlify]: https://app.netlify.com/sites/ohif/deploys -[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml +[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/platform/app/netlify.toml [build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh diff --git a/platform/docs/docs/deployment/cors.md b/platform/docs/docs/deployment/cors.md new file mode 100644 index 00000000000..bd88d0d1daa --- /dev/null +++ b/platform/docs/docs/deployment/cors.md @@ -0,0 +1,313 @@ +--- +sidebar_position: 6 +--- + +# Cross-Origin Information for OHIF + +This document describes various security configurations, settings and environments/contexts needed to fully leverage OHIF’s capabilities. One may need some configurations while others might need ALL of them - it all depends on the environment OHIF is expected to run in. + +In particular, three of OHIF’s features depend on these configurations: +- [OHIF’s use of SharedArrayBuffer](#sharedarraybuffer) +- [Embedding OHIF in an iframe](#embedding-ohif-in-an-iframe) +- [XMLHttpRequests to fetch data from data sources](#cors-in-ohif) + + + +## SharedArrayBuffer +A `SharedArrayBuffer` is a JavaScript object that is similar to an `ArrayBuffer` but can be shared between web workers and the window that spawned them via the `postMessage` API. See [SharedArrayBuffer in MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) for more information. + +:::tip +To turn off Shared Array Buffer completely, just set `useSharedArrayBuffer` to `false` in the [OHIF configuration](../configuration/configurationFiles.md). But keep in mind that you will not get the performance boost that Shared Array Buffer offers for decoding and rendering big volumes where web workers write to the same memory space. +::: + +### Security Requirements + +In order to use `SharedArrayBuffer` objects in the browser, the following security conditions must be met: + +- The page must be served in a [secure context](#secure-context). +- The page must have [cross-origin isolation](#cross-origin-isolation) enabled. + +### `SharedArrayBuffer` in OHIF + +OHIF uses `SharedArrayBuffer` in its volume loader (from Cornerstone3D). It comes with the benefit of improved performance and optimization at the cost of some configuration to use it. + +As such, if the following popup is shown when launching OHIF then the OHIF server will have to be configured to permit the loading of volumetric images and data. Note that stack viewports are still available and functional even when this error is present. + +![OHIF in non-secure context](../assets/img/ohif-non-secure-context.png) + +To better determine which (if not all) of the [security requirements](#security-requirements) are lacking, have a look at the browser console. + +Output in the console similar to the following indicates that OHIF is not running in a [secure context](#secure-context). + +![browser console for non-secure context](../assets/img/browser-console-non-secure-context.png) + +Absence of the above error in the console together with the presence of the Cross Origin Isolation popup warning, likely indicates that either or both of the [COOP](#coop---cross-origin-opener-policy) and/or [COEP](#coep---cross-origin-embedder-policy) headers are not set for OHIF. + +## Embedding OHIF in an iframe + +As described [here](./iframe.md), there are cases where OHIF will be embedded in an iframe. The following links provide more information for setting up and configuring OHIF to work in an iframe: + +- [OHIF iframe documentation](./iframe.md#static-build) +- [OHIF as a Cross-origin Resource in an iframe](#ohif-as-a-cross-origin-resource-in-an-iframe) + +## Secure Context + +MDN defines a secure context as [“a Window or Worker for which certain minimum standards of authentication and confidentiality are met.“](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) + +Any local URL is considered secure. The following are some examples of local URLs that are considered secure… +- http://localhost +- http://127.0.0.1:3000 + +URLs that are NOT local must be delivered over `https://` or `wss://` (i.e. TLS) to be considered secure. See [When is a context considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure) in MDN for more information. + +### iframes + +A page embedded in an iframe is considered secure if it itself and every one of its embedding ancestors are delivered securely. Otherwise it is deemed insecure. + +### Why does OHIF require a secure context? + +Beyond all of the inherent benefits of a secure connection, OHIF requires a secure context so that it can utilize [SharedArrayBuffer](#sharedarraybuffer) objects for volume rendering. + +### Configuring/setting up a secure context + +[Local URLs are considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure), and as such whenever OHIF is accessed via a local URL (e.g. http://localhost:3000) it is running in a secure context. For example, in a development environment using the default webpack setup, OHIF can be deployed and accessed in a secure context at http://localhost:3000. + +The best alternative is to host OHIF over HTTPS. + +:::tip +OHIF can be served over HTTPS in a variety of ways (these are just some examples). +- Website hosting services that offer HTTPS deployment (e.g,. Netlify) or offer HTTPS load balancers (AWS, Google Cloud etc.) +- Setting up a reverse proxy (e.g. `nginx`) with a self-signed certificate that forwards requests to the OHIF server + - [An OHIF Docker image can be set up this way](./docker.md#ssl). +::: + +## Origin Definition + +According to [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Origin), a Web content’s origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match. + +## CORS - Cross-Origin Resource Sharing + +A cross-origin resource is a resource (e.g. image, JSON, etc) that is served by one origin and used/referenced by a different origin. + +CORS is the protocol utilized by web servers and browsers whereby a server of one origin identifies and/or restricts which of its resources that other origins (i.e. other than its own) a browser should allow access to. By default a browser does not permit cross-origin resource sharing. + +The CORS mechanism relies on the HTTP response headers from the server to indicate if a resource can be shared with a different origin. + +See the [MDN CORS article](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more information. + +### CORS HTTP Headers + +The header that mostly concerns OHIF is listed below and should be configured accordingly on the DICOMweb server or any data source that OHIF would make XMLHttpRequests to for its data. + +```http +Access-Control-Allow-Origin: `` | * +``` + +:::tip +The `Access-Control-Allow-Origin` header specifies which origins can access the served resource embedded in the response. + +Either a single, specific origin (i.e. ``) can be specified or ALL origins (i.e. *) + +See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-allow-origin) for more information. +::: + +### CORS in OHIF + +OHIF fetches and displays data and images from data sources. It invokes XMLHttpRequests to some data sources such as DICOMweb data sources to fetch the information to render. Typically, a DICOMweb server is hosted on a completely different origin than the one serving OHIF. As such, those XMLHttpRequests use CORS. + +### Troubleshooting CORS in OHIF + +The following is an example screenshot of the browser console when one of OHIF’s DICOMweb data source servers is not configured for CORS. + +![CORS browser console errors](../assets/img/cors-browser-console-errors.png) + +And the following is what is in the accompanying network tab. + +![CORS browser network panel errors](../assets/img/cors-network-panel-errors.png) + +:::info +Setting the appropriate CORS header varies per server or service that is hosting the data source. What follows below is just one example to remedy the problem. +::: + +:::tip +If Orthanc is the data source running in a Docker container composed with/behind nginx. And OHIF is being served at localhost:3000. The issue can be remedied by adding either of the following to Orthanc’s Docker container nginx.conf file. + +```nginx +add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; +``` + +Or + +```nginx +add_header 'Access-Control-Allow-Origin' '*' always; +``` +::: + +## COOP - Cross-Origin Opener Policy + +The COOP HTTP response header restricts the global, root document of the page from being referenced and accessed by another cross-origin document that might open the page in a window. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) for more information. + +### Header Values Pertinent to OHIF (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy#syntax) for more information) + +|Value|Description| +|-----|-----------| +|same-origin|Restricts the document to be referenced by openers of the same origin only.| + +### COOP in OHIF + +COOP is required for [SharedArrayBuffer](#sharedarraybuffer) usage in OHIF. See also [Troubleshooting Cross-origin Isolation in OHIF](#troubleshooting-cross-origin-isolation-in-ohif). + +## COEP - Cross-Origin Embedder Policy + +The COEP HTTP response header restricts cross-origin documents from being embedded into a document (e.g. in an iframe, video, image, etc). See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) for more information. + +### Header Values Pertinent to OHIF (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy#syntax) for more information) + +|Value|Description| +|-----|-----------| +|require-corp|Permits the document to load either of the following embedded resources:
  • Those from the same origin
  • Cross-origin resources embedded by a DOM element that has the appropriate [crossorigin attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) set
  • Cross-origin resources with the appropriate [CORP response header](#corp---cross-origin-resource-policy)
+|credentialless|See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy#syntax) for more information| + +### COEP in OHIF + +COEP is required for [SharedArrayBuffer](#sharedarraybuffer) usage in OHIF. See also [Troubleshooting Cross-origin Isolation in OHIF](#troubleshooting-cross-origin-isolation-in-ohif). + +## Cross-origin Isolation + +Cross-origin isolation is [enabled](https://web.dev/cross-origin-isolation-guide/#enable-cross-origin-isolation) for a web page when the following COOP and COEP headers are set. +- [COOP](#coop---cross-origin-opener-policy) with `same-origin` +- [COEP](#coep---cross-origin-embedder-policy) with `require-corp` or `credentialless` + +### iframe + +An iframe is considered to have cross-origin isolation enabled if it itself has the appropriate COOP and COEP headers set as well as every one of its embedding ancestors. + +### Troubleshooting Cross-origin Isolation in OHIF + +The [SharedArrayBuffer in OHIF](#sharedarraybuffer-in-ohif) section describes how to determine if there are problems with cross-origin isolation in OHIF. If it is determined that COOP and/or COEP is indeed an issue, then the COOP and COEP headers must be set for OHIF. How to accomplish this varies per server or service that is hosting OHIF. The following are just a few examples. + +:::tip +In the default dev environment, the following can be set in the webpack.pwa.js file… + +```javascript +devServer: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp" + } +} +``` +::: + +:::tip +If deploying OHIF using Netlify, the Netlify configuration [file](https://docs.netlify.com/configure-builds/file-based-configuration/) can be used to configure the headers as such… + +``` +[[headers]] + # Define which paths this specific [[headers]] block will cover. + for = "/*" + + [headers.values] + Cross-Origin-Opener-Policy = "same-origin" + Cross-Origin-Embedder-Policy = "require-corp" +``` +::: + +:::tip +If OHIF is served behind nginx, then the headers can be set in the nginx.conf file as follows. The [template nginx configuration file](https://github.com/OHIF/Viewers/blob/master/.docker/Viewer-v3.x/default.conf.template) for creating a [OHIF Docker image](./docker.md#building-the-docker-image) has an example of this too. +```nginx +server { + location / { + add_header Cross-Origin-Opener-Policy same-origin; + add_header Cross-Origin-Embedder-Policy require-corp; + } +} +``` +::: + +## CORP - Cross-Origin Resource Policy + +The CORP HTTP response header indicates which origins can read and use a resource. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy) for more information. + +### Header Values (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#usage) for more information) + +|Value|Description| +|-----|-----------| +|same-site|Only requests from the same site can read the resource.| +|same-origin|Only requests from the same origin can read the resource.| +|cross-origin|Requests from any origin can read the resource. The value is useful and [exists](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#relationship_to_cross-origin_embedder_policy_coep) primarily for letting documents with the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1) know that the resource is ok to be embedded| + +### OHIF and CORP + +There are two scenarios where the CORP header is relevant to OHIF: + +- [PDF from a Cross Origin DICOMweb Data Source](#pdf-from-a-cross-origin-dicomweb-data-source) +- [OHIF as a Cross-origin Resource in an iframe](#ohif-as-a-cross-origin-resource-in-an-iframe) + +Both these scenarios stem from the fact that OHIF has to be served with the [COEP](#coep---cross-origin-embedder-policy) header to support [SharedArrayBuffer](#sharedarraybuffer). + +#### PDF from a Cross Origin DICOMweb Data Source + +There are some DICOMweb data sources (e.g. dcm4chee) whereby OHIF uses the data source’s `/rendered` endpoint to embed a DICOM PDF document in the OHIF DOM using an `` tag. + +As specified for the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1), a page like OHIF with COEP header `require-corp` can embed cross-origin resources in DOM elements that have the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) OR the resource is delivered with an appropriate CORP header. The `` tag does NOT support the `crossorigin` attribute. As such, the PDF must be delivered with a CORP header. + +:::tip +Setting the CORP header varies per server or service that is hosting the data source. The following is just one example. + +For a dcm4chee DICOMweb data source composed in Docker behind nginx, the CORP header can be configured in the nginx.conf file as such: + +```nginx +add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always; +``` + +If the dcm4chee server and the OHIF server are hosted on the same site, then the following would also work: +```nginx +add_header 'Cross-Origin-Resource-Policy' 'same-site' always; +``` +::: + +#### OHIF as a Cross-origin Resource in an iframe + +There are cases where [OHIF is embedded in an iframe](./iframe.md) and the embedding page is from a different origin. Again due to the [security requirements for SharedArrayBuffer](#security-requirements), [both OHIF and the embedding page](#iframe) must have the appropriate COEP header. In this scenario, OHIF is the cross-origin resource and since the ` + + + + +[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration +[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc + +[config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/src/config.js +[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile +[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf +[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json +[config-keycloak]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json + diff --git a/platform/docs/docs/development/architecture.md b/platform/docs/docs/development/architecture.md index d135fcd2b38..e3760240d46 100644 --- a/platform/docs/docs/development/architecture.md +++ b/platform/docs/docs/development/architecture.md @@ -24,16 +24,17 @@ you'll see the following: ```bash │ ├── extensions -│ ├── _example # Skeleton of example extension │ ├── default # default functionalities │ ├── cornerstone # 2D/3D images w/ Cornerstonejs │ ├── cornerstone-dicom-sr # Structured reports │ ├── measurement-tracking # measurement tracking │ └── dicom-pdf # View DICOM wrapped PDFs in viewport +| # and many more ... │ ├── modes │ └── longitudinal # longitudinal measurement tracking mode | └── basic-dev-mode # basic viewer with Cornerstone (a developer focused mode) +| # and many more │ ├── platform │ ├── core # Business Logic diff --git a/platform/docs/docs/development/continous-integration.md b/platform/docs/docs/development/continuous-integration.md similarity index 100% rename from platform/docs/docs/development/continous-integration.md rename to platform/docs/docs/development/continuous-integration.md diff --git a/platform/docs/docs/development/getting-started.md b/platform/docs/docs/development/getting-started.md index 5f631a5d310..cafb0f538a8 100644 --- a/platform/docs/docs/development/getting-started.md +++ b/platform/docs/docs/development/getting-started.md @@ -99,7 +99,7 @@ You should see the following output: ### 🎉 Celebrate 🎉
- +
### Building for Production diff --git a/platform/docs/docs/development/link.md b/platform/docs/docs/development/link.md new file mode 100644 index 00000000000..733ae5d0f2b --- /dev/null +++ b/platform/docs/docs/development/link.md @@ -0,0 +1,62 @@ +--- +sidebar_position: 8 +sidebar_label: Local Linking +--- + +# Introduction + +Local linking allows you to develop and test a library in the context of an application before it's published or when you encounter +a bug that you suspect is related to a library. With Yarn, this can be achieved through the yarn link command. + +The general procedure is as follows: + + +Link the Library: + +```sh +cd /path/to/library +yarn link +``` + +This command will create a symlink in a global directory for the library. + + +Link to the Application: + +```sh +cd /path/to/application +yarn link "library-name" +``` + +Creates a symlink from the global directory to the application's node_modules. + + +# Tutorial for linking Cornerstone3D to OHIF + +Below we demonstrate how to link Cornerstone3D to OHIF Viewer. This is useful for testing and debugging Cornerstone3D in the context of OHIF Viewer. + +
+ +
+ +::tip +Since `@cornerstonejs/tools` depends on `@cornerstonejs/core`, if you need the changes +you made in `@cornerstonejs/core` to be reflected in `@cornerstonejs/tools`, you need to +also link `@cornerstonejs/core` to `@cornerstonejs/tools`. + +```sh +cd /path/to/cornerstonejs-core +# for the core +yarn link + +cd /path/to/cornerstonejs-tools +yarn link "@cornerstonejs/core" + +# for the tools +yarn link + +# inside OHIF +cd /path/to/OHIFViewer +yarn link "@cornerstonejs/core" +yarn link "@cornerstonejs/tools" +``` diff --git a/platform/docs/docs/development/ohif-cli.md b/platform/docs/docs/development/ohif-cli.md index 7511e2bc73d..0cd7ebc6aad 100644 --- a/platform/docs/docs/development/ohif-cli.md +++ b/platform/docs/docs/development/ohif-cli.md @@ -113,7 +113,7 @@ files: ### create-extension -Similar to the `create-extension` command, you can use the `create-extension` +Similar to the `create-mode` command, you can use the `create-extension` command to create a new extension template. This command will create a new extension template in the directory that you specify the path. diff --git a/platform/docs/docs/development/our-process.md b/platform/docs/docs/development/our-process.md index d8b8ed86217..aaac77533d2 100644 --- a/platform/docs/docs/development/our-process.md +++ b/platform/docs/docs/development/our-process.md @@ -101,7 +101,7 @@ quality and test coverage must not be changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video recordings of end-to-end tests are stored for later review. -[You can read more about our continous integration efforts here](/development/continous-integration.md) +[You can read more about our continuous integration efforts here](/development/continuous-integration.md) ## Releases diff --git a/platform/docs/docs/faq.md b/platform/docs/docs/faq.md index 67b7d30b2e8..01d42ee9510 100644 --- a/platform/docs/docs/faq.md +++ b/platform/docs/docs/faq.md @@ -29,6 +29,9 @@ If you have resources and would like to fund the development of a feature, please [contact us](https://www.ohif.org) or work with community members that offer [consulting services][commercial-support]. +### Why do I keep seeing a Cross Origin Isolation warning +If you encounter a warning while running OHIF indicating that your application is not cross-origin isolated, it implies that volume rendering, such as MPR, will not function properly since they depend on Shared Array Buffers. To resolve this issue, we recommend referring to our comprehensive guide on Cross Origin Isolation available at [./deployment/cors.md](./deployment/cors.md). + ### Who should I contact about Academic Collaborations? [Gordon J. Harris](https://www.dfhcc.harvard.edu/insider/member-detail/member/gordon-j-harris-phd/) @@ -37,11 +40,10 @@ collaborators. We are always happy to hear about new groups interested in using the OHIF framework, and may be able to provide development support if the proposed collaboration has an impact on cancer research. -### Does OHIF offer commercial support? +### Does OHIF offer support? + +yes, you can contact us for more information [here](https://ohif.org/get-support) -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. ### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? diff --git a/platform/docs/docs/migration-guide.md b/platform/docs/docs/migration-guide.md index ac1d2fce488..fcf45aadd3a 100644 --- a/platform/docs/docs/migration-guide.md +++ b/platform/docs/docs/migration-guide.md @@ -1,6 +1,6 @@ --- sidebar_position: 10 -sidebar_label: 💥 Migration Guide (NEW)💥 +sidebar_label: Migration Guide (NEW) --- # Migration Guide @@ -89,6 +89,16 @@ Since the platform/viewer (@ohif/viewer) is already at v4.12.51, we opted to ren ## Configuration +:::tip +There are various configurations available to customize the viewer. Each configuration is represented by a custom-tailored object that should be used with the viewer to work effectively with a specific server. Here are some examples of configuration files found in the platform/app/public/config directory. Some server-specific configurations that you should be aware are: `supportsWildcard`, `bulkDataURI`, `omitQuotationForMultipartRequest`, `staticWado` (Read more about them [here](./configuration/configurationFiles.md)). + +- default.js: This is our default configuration designed for our main server, which uses a Static WADO datasource hosted on Amazon S3. +- local_orthanc.js: Use this configuration when working with our local Orthanc server. +- local_dcm4chee.js: This configuration is intended for our local dcm4chee server. +- netlify.js: This configuration is the same as default.js and is used for deployment on Netlify. +- google.js: Use this configuration to run the viewer against the Google Health API. +::: + OHIF v3 has a new configuration structure. The main difference is that the `servers` is renamed to `dataSources` and the configuration is now asynchronous. Datasources are more abstract and far more capable than servers. Read more about dataSources [here](./platform/extensions/modules/data-source.md). @@ -98,6 +108,7 @@ far more capable than servers. Read more about dataSources [here](./platform/ext - The maxConcurrentMetadataRequests property has been removed in favor of `maxNumRequests` - The hotkeys array has been updated with different command names and options, and some keys have been removed. - New properties have been added, including `maxNumberOfWebWorkers`, `omitQuotationForMultipartRequest`, `showWarningMessageForCrossOrigin`, `showCPUFallbackMessage`, `showLoadingIndicator`, `strictZSpacingForVolumeViewport`. +- you should see if `supportsWildcard` is supported in your server, some servers don't support it and you need to make it false. ## Modes @@ -924,7 +935,7 @@ We have gone through extensive re-design of each part of the UI, and we have als
-I have a huge complex styles using native CSS, how can I re-use them? +I have a huge complex styles using native CSS, how can I reuse them? You can leverage the power of Tailwind CSS (https://TailwindCSS.com/) in OHIF v3 to reuse your existing styles. Tailwind CSS is a utility-first approach, allowing you to create reusable CSS classes by composing utility classes together. You can migrate your existing styles to Tailwind CSS by breaking them down into utility classes and utilizing the extensive set of predefined utilities provided by Tailwind CSS. diff --git a/platform/docs/docs/platform/environment-variables.md b/platform/docs/docs/platform/environment-variables.md index 4fd2691a4aa..1b08d54925f 100644 --- a/platform/docs/docs/platform/environment-variables.md +++ b/platform/docs/docs/platform/environment-variables.md @@ -11,7 +11,7 @@ There are a number of environment variables we use at build time to influence th NODE_ENV=< production | development > DEBUG=< true | false > APP_CONFIG=< relative path to application configuration file > -PUBLIC_URL=<> +PUBLIC_URL=< relative path to application root - default / > VERSION_NUMBER= BUILD_NUM= # i18n diff --git a/platform/docs/docs/platform/extensions/index.md b/platform/docs/docs/platform/extensions/index.md index cf60ca5fef6..ec8f48d18c2 100644 --- a/platform/docs/docs/platform/extensions/index.md +++ b/platform/docs/docs/platform/extensions/index.md @@ -51,7 +51,7 @@ export default { */ id, - // Lifecyle + // Lifecycle preRegistration() { /* */ }, onModeEnter() { /* */ }, onModeExit() { /* */ }, @@ -182,7 +182,7 @@ Use the provided `cli` to add/remove/install/uninstall extensions. Read more [he The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). -After an extension gets registered withing the `viewer`, +After an extension gets registered within the `viewer`, each [module](#modules) defined by the extension becomes available to the modes via the `ExtensionManager` by requesting it via its id. [Read more about Extension Manager](#extension-manager) @@ -206,7 +206,7 @@ used to initialize data. [`onModeExit`](./lifecycle#onModeExit): Similarly to onModeEnter, this hook is called when navigating away from a mode, or before a mode’s data or datasource -is changed. This can be used to cache data for re-use later, but since it +is changed. This can be used to cache data for reuse later, but since it isn't known which mode will be entered next, the state after exiting should be clean, that is, the same as the state on a clean start. This is called BEFORE service clean up, and after mode specific onModeExit handling. diff --git a/platform/docs/docs/platform/extensions/modules/data-source.md b/platform/docs/docs/platform/extensions/modules/data-source.md index 0d7d855616b..6cbb741d36d 100644 --- a/platform/docs/docs/platform/extensions/modules/data-source.md +++ b/platform/docs/docs/platform/extensions/modules/data-source.md @@ -61,11 +61,9 @@ You can add your custom datasource by creating the implementation using `IWebApiDataSource.create` from `@ohif/core`. This factory function creates a new "Web API" data source that fetches data over HTTP. -You need to make sure, you implement the following functions for the data -source. - ```js title="platform/core/src/DataSources/IWebApiDataSource.js" function create({ + initialize, query, retrieve, store, @@ -80,7 +78,18 @@ function create({ ``` You can take a look at `dicomweb` data source implementation to get an idea -`extensions/default/src/DicomWebDataSource/index.js` +`extensions/default/src/DicomWebDataSource/index.js` but here here are some +important api endpoints that you need to implement: + + +- `initialize`: This method is called when the data source is first created in the mode.tsx, it is used to initialize the data source and set the configuration. For instance, `dicomwebDatasource` uses this method to grab the StudyInstanceUID from the URL and set it as the active study, as opposed to `dicomJSONDatasource` which uses url in the browser to fetch the data and store it in a cache +- `query.studies.search`: This is used in the study panel on the left to fetch the prior studies for the same MRN which is then used to display on the `All` tab. it is also used in the Worklist to show all the studies from the server. +- `query.series.search`: This is used to fetch the series information for a given study that is expanded in the Worklist. +- `retrieve.bulkDataURI`: used to render RTSTUCTURESET in the viewport. +- `retrieve.series.metadata`: It is a crucial end point that is used to fetch series level metadata which for hanging displaySets and displaySet creation. +- `store.dicom`: If you don't need store functionality, you can skip this method. This is used to store the data in the backend. + + ## Static WADO Client @@ -100,3 +109,66 @@ In `OHIF-v3` we have a central location for the metadata of studies, and they ar located in `DicomMetadataStore`. Your custom datasource can communicate with `DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will learn more about `DicomMetadataStore` in services. + +## Adding a Data Source Outside a Module + +A data source can be added outside a module via `ExtensionManager.addDataSource`. +The following snippet of code demonstrates how `addDataSource` can be used to add +a new DICOMWeb data source for the Google Cloud Healthcare API and set it as the +active data source. + +```js +extensionManager.addDataSource({ + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'google', + configuration: { + friendlyName: 'dcmjs DICOMWeb Server', + name: 'GCP', + wadoUriRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + wadoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: false, + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, + {activate:true} +}); +``` + +## Updating a Data Source's Configuration + +An existing data source can have its configuration updated using the +`ExtensionManager.updateDataSourceConfiguration` method. The following snippet of +code demonstrates how `updateDataSourceConfiguration` can be use to update the +configuration of an existing DICOMWeb data source (named `dicomweb`) with the +configuration for a Google Cloud Healthcare API data source. + +```js +extensionManager.updateDataSourceConfiguration( "dicomweb", + { + name: 'GCP', + wadoUriRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + wadoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: false, + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, +); +``` diff --git a/platform/docs/docs/platform/extensions/modules/hpModule.md b/platform/docs/docs/platform/extensions/modules/hpModule.md index 67bbd7ec824..922d9e04ed1 100644 --- a/platform/docs/docs/platform/extensions/modules/hpModule.md +++ b/platform/docs/docs/platform/extensions/modules/hpModule.md @@ -36,7 +36,6 @@ Here is an example protocol which if used will hang a 1x3 layout with the first const oneByThreeProtocol = { id: 'oneByThreeProtocol', locked: true, - hasUpdatedPriorsInformation: false, name: 'Default', createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2022-10-04T19:22:08.894Z', @@ -241,12 +240,31 @@ A list of criteria for the protocol along with the provided points for ranking. "StudyDescription", "ModalitiesInStudy", "NumberOfStudyRelatedSeries", "NumberOfSeriesRelatedInstances" In addition to these tags, you can also use a custom attribute that you have registered before. We will learn more about this later. + - `from`: Indicates the source of the attribute. This allows getting values + from other objects such as the `prior` instance object instead of from the + current one. - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] + - | Rule | Single Value | Array Value | Example | + |--- |--- |--- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | equals | === | All members are === in same order | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Fail)

= ['abc'] (Fail)

= ['abc', 'def', 'GHI'] (Valid)

= ['abc', 'GHI', 'def'] (Fail)

= ['abc', 'def'] (Fail)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Valid)
= 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)
| + | doesNotEqual | !== | Any member is !== for the array, either in value, order, or length | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Valid)
= ['abc'] (Valid)
= ['abc', 'def', 'GHI'] (Fail)
= ['abc', 'GHI', 'def'] (Valid)
= ['abc', 'def'] (Valid)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Fail)Valid

= 'Attenuation' (Valid)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Fail)
= 'Attenuation Corrected' (Fail)
= 'Attenuation' (Fail) | + | includes | Not allowed | Value is equal to one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ['abc'] (Valid)

= ‘abc’ (Fail)

= [‘abc’] (Fail)

= ‘dog’ (Fail)

= = [‘att’, ‘abc’] (Valid)

= ['abc', 'def', 'dog'] (Valid)

= ['cat', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail)


value = ['Attenuation Corrected']
testValue = 'Attenuation Corrected' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail) | + | doesNotInclude | Not allowed | Value is not in one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ‘Corr’ (Valid)

= ‘abc’ (Fail)

= [‘att’, ‘cor’] (Valid)

= ['abc', 'def', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid)


value = ['Attenuation Corrected']
testValue = 'Attenuation' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid) | + | containsI | String containment (case insensitive) | String containment (case insensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)



value = ['abc', 'def', 'GHI']
testValue = 'def' (Valid)

= 'dog' (Fail)

= ['gh', 'de'] (Valid)

= ['cat', 'dog'] (Fail)
| + | contains | String containment (case sensitive) | String containment (case sensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Fail)
= [‘att’, ‘cor’] (Fail)
= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']

testValue = 'def' (Valid)
= 'dog' (Fail)

= ['cat', 'de'] (Valid)

= ['cat', 'dog'] (Fail) | + | doesNotContain | String containment is false | String containment is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)


= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)

value = ['abc', 'def', 'GHI']
testValue = 'def' (Fail)


= 'dog' (Valid)

= ['cat', 'de'] (Fail)

= ['cat', 'dog'] (Valid) | + | doesNotContainI | String containment is false (case insensitive) | String containment (case insensitive) is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘corr’ (Fail)

= [‘att’, ‘cor’] (Fail)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)


value = ['abc', 'def', 'GHI']
testValue = 'DEF' (Fail)

= 'dog' (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Valid) | + | startsWith | Value begins with characters | Starts with one of the values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘Att’ (Fail)

= ['cat', 'dog', 'Att'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'GH'] (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Fail) | + | endsWith | Value ends with characters | ends with one of the value of the array | value = 'Attenuation Corrected'
testValue = ‘TED’ (Fail)

= ‘ted’ (Valid)

= ['cat', 'dog', 'ted'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'HI'] (Valid)

= ['cat', 'hi'] (Fail)

= ['cat', 'dog'] (Fail) | + | greaterThan | value is => to rule | Not applicable | value = 30

testValue = 20 (Valid)
= 40 (Fail)

| + | lessThan | value is <= to rule | Not applicable | value = 30

testValue = 40 (Valid)
= 20 (Fail)
| + | range | Not applicable | 2 value requested (min and max) | value = 50

testValue = [10,60] (Valid)
= [60, 10] (Valid)

= [0, 10] (Fail)

= [70, 80] (Fail)

= 45 (Fail)

= [45] (Fail) | + | notNull | Not Applicable | Not Applicable | No value | A sample of the matching rule is above which matches against the study description to be PETCT ```js @@ -263,6 +281,20 @@ A list of criteria for the protocol along with the provided points for ranking. }, ``` +### `from` attribute +The from attribute allows getting the attribute to test from some other object +such as the prior study, the list of studies overall or another module provided +value. Some of the possible attributes are: + +* `prior`: To get the value from the prior study. +* `activeStudy`: To match the active study +* `studies`: To match the list of studies to display +* `displaySets`: The display sets for the current study +* `allDisplaySets`: Alll available display sets +* `instance`: An instance from the current display set being tested +* `options`: Gets the options object itself, eg if you want a simple top level + value. + ### displaySetSelectors Defines the display sets that the protocol will use for arrangement. @@ -416,6 +448,7 @@ As you can see in the hanging protocol we defined three viewports (but only show - `toolGroupId`: tool group that will be used for the viewport (optional) - `initialImageOptions`: initial image options (optional - can be specific imageIndex number or preset (first, middle, last)) - `syncGroups`: sync groups for the viewport (optional) + -The `displayArea` parameter refers to the designated area within the viewport where a specific portion of the image can be displayed. This parameter is optional and allows you to choose the location of the image within the viewport. For example, in mammography images, you can display the left breast on the left side of the viewport and the right breast on the right side, with the chest wall positioned in the middle. To understand how to define the display area, you can refer to the live example provided by CornerstoneJS [here](https://www.cornerstonejs.org/live-examples/programaticpanzoom). 2. `displaySets`: defines the display sets that are displayed on a viewport. It is an array of objects, each object being one display set. @@ -423,7 +456,7 @@ As you can see in the hanging protocol we defined three viewports (but only show - `options` (optional): options for the display set - voi: windowing options for the display set (optional: windowWidth, windowCenter) - voiInverted: whether the VOI is inverted or not (optional) - - colormap: colormap for the display set (optional, it is an object with `{ name }` and optional extra `opacityMapping` property) + - colormap: colormap for the display set (optional, it is an object with `{ name }` and optional extra `opacity` property) - displayPreset: display preset for the display set (optional, used for 3D volume rendering. e.g., 'CT-Bone') @@ -459,22 +492,70 @@ HangingProtocolService.addCustomAttribute( ``` - ## Matching on Prior Study with UID Often it is desired to match a new study to a prior study (e.g., follow up on a surgery). Since the hanging protocols run on displaySets we need to have a way to let OHIF knows that it needs to load the prior study as well. This can -be done by specifying both StudyInstanceUIDs in the URL. Below we are -running OHIF with two studies +be done by specifying both StudyInstanceUIDs in the URL. The additional studies +are then accessible to the hanging protocol. Below we are +running OHIF with two studies, and a comparison hanging protocol available by +default. ```bash -http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1 +http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1&hangingprotocolId=@ohif/hpCompare +``` + +The `&hangingProtocolId` option forces the specific hanging protocol to be +applied, but the mode can also add the hanging protocols to the default set, +and then the best matching hanging protocol will be applied by the run method. + +To match any other studies, it is required to enable the prior matching rules +capability using: + +```javascript + // Indicate number of priors used - 0 means any number, -1 means none. + numberOfPriorsReferenced: 1, ``` -Now you have access to both studies and you can use matchingRules to match -displaySets. +The matching rule that allows the hanging protocol to be runnable is: +```javascript + protocolMatchingRules: [ + { + id: 'Two Studies', + weight: 1000, + // This will generate 1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1 + // since that is study instance UID in the prior from instance. + attribute: 'StudyInstanceUID', + // The 'from' attribute says where to get the 'attribute' value from. In this case + // prior means the second study in the study list. + from: 'prior', + required: true, + constraint: { + notNull: true, + }, + }, + ], +``` +The display set selector selecting the specific study to display is included +in the studyMatchingRules. Note that this rule will cause ONLY the second study +to be matched, so it won't attempt to match anything in other studies. +Additional series level criteria, such as modality rules must be included at the +`seriesMatchingRules`. -Our roadmap includes enabling matching on prior studies without the UID (e.g., baseline, most recent and index). +```javascript + studyMatchingRules: [ + { + // The priorInstance is a study counter that indicates what position this study is in + // and the value comes from the options parameter. + attribute: 'studyInstanceUIDsIndex', + from: 'options', + required: true, + constraint: { + equals: { value: 1 }, + }, + }, + ], +``` diff --git a/platform/docs/docs/platform/extensions/modules/layout-template.md b/platform/docs/docs/platform/extensions/modules/layout-template.md index 66c839dae1b..821444975b8 100644 --- a/platform/docs/docs/platform/extensions/modules/layout-template.md +++ b/platform/docs/docs/platform/extensions/modules/layout-template.md @@ -120,7 +120,7 @@ function ViewerLayout({ commandsManager={commandsManager} /> - {/* Rigth SIDEPANELS */} + {/* Right SIDEPANELS */} { const wrappedViewport = props => { return ( @@ -43,20 +43,13 @@ const getViewportModule = () => { A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which creates a cornerstone viewport: -:::note Tip - -Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to -the previous version (3.0) which uses `displaySet`. This is due to the fact that -we are moving to a new data model that can render fused images in a single viewport. - -::: ```jsx function TrackedCornerstoneViewport({ children, dataSource, displaySets, - viewportIndex, + viewportId, servicesManager, extensionManager, commandsManager, @@ -87,6 +80,45 @@ function TrackedCornerstoneViewport({ } ``` +### Viewport re-rendering optimizations + +We make use of the React memoization pattern to prevent unnecessary re-renders +for the viewport unless certain aspects of the Viewport props change. You can take +a look into the `areEqual` function in the `OHIFCornerstoneViewport` component to +see how this is done. + +```js +function areEqual(prevProps, nextProps) { + if (prevProps.displaySets.length !== nextProps.displaySets.length) { + return false; + } + + if ( + prevProps.viewportOptions.orientation !== + nextProps.viewportOptions.orientation + ) { + return false; + } + + // rest of the code +``` + +as you see, we check if the `needsRerendering` prop is true, and if so, we will +re-render the viewport if the `displaySets` prop changes or the orientation +changes. + + +We use viewportId to identify a viewport and we use it as a key in React +rendering. This is important because it allows us to keep track of the viewport +and its state, and also let React optimize and move the viewport around in the +grid without re-rendering it. However, there are some cases where we need to +force re-render the viewport, for example, when the viewport is hydrated +with a new Segmentation. For these cases, we use the `needsRerendering` prop +to force re-render the viewport. You can add it to the `viewportOptions` + + + + ### `@ohif/app` diff --git a/platform/docs/docs/platform/internationalization.md b/platform/docs/docs/platform/internationalization.md index 1bb32de5ba5..8caf9a729eb 100644 --- a/platform/docs/docs/platform/internationalization.md +++ b/platform/docs/docs/platform/internationalization.md @@ -182,7 +182,7 @@ the following file tree: index.js | UK |-- Buttons.js - indes.js + index.js | US |-- Buttons.js index.js @@ -347,7 +347,7 @@ https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78 Chinese: https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=zh -Portugese: +Portuguese: https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=pt-BR Here are some links you can use to sign up to help translate. All you have to do diff --git a/platform/docs/docs/platform/managers/extension.md b/platform/docs/docs/platform/managers/extension.md index 9fb5196f840..a31f2244219 100644 --- a/platform/docs/docs/platform/managers/extension.md +++ b/platform/docs/docs/platform/managers/extension.md @@ -21,13 +21,23 @@ const extensionManager = new ExtensionManager({ appConfig, }); ``` +## Events +The following events get published by the `ExtensionManager`: -The `ExtensionManager` only has a few public members: +| Event | Description | +| ---------------------------- | ------------------------------------------------------ | +| ACTIVE_DATA_SOURCE_CHANGED | Fired when the active data source is changed - either replaced with an entirely different one or the existing active data source gets its definition changed via `updateDataSourceConfiguration`. | + +## API +The `ExtensionManager` only has the following public API: - `setActiveDataSource` - Sets the active data source for the application - `getDataSources` - Returns the registered data sources - `getActiveDataSource` - Returns the currently active data source - `getModuleEntry` - Returns the module entry by the give id. +- `addDataSource` - Dynamically adds a data source and optionally sets it as the active data source +- `updateDataSourceConfiguration` - Updates the configuration of a specified data source (name). +- `getDataSourceDef` - Gets the data source definition for a particular data source name. ## Accessing Modules diff --git a/platform/docs/docs/platform/modes/index.md b/platform/docs/docs/platform/modes/index.md index f2c32b91ce4..35b046d67fe 100644 --- a/platform/docs/docs/platform/modes/index.md +++ b/platform/docs/docs/platform/modes/index.md @@ -191,7 +191,7 @@ export default mode; ### Consuming Extensions As mentioned in the [Extensions](../extensions/index.md) section, in `OHIF-v3` -developers write their extensions to create re-usable functionalities that later +developers write their extensions to create reusable functionalities that later can be used by `modes`. Now, it is time to describe how the registered extensions will get utilized for a workflow mode via its `id`. diff --git a/platform/docs/docs/platform/services/data/DisplaySetService.md b/platform/docs/docs/platform/services/data/DisplaySetService.md index 12136e987c6..8b8bcae671d 100644 --- a/platform/docs/docs/platform/services/data/DisplaySetService.md +++ b/platform/docs/docs/platform/services/data/DisplaySetService.md @@ -8,6 +8,14 @@ sidebar_label: DisplaySet Service ## Overview `DisplaySetService` handles converting the `instanceMetadata` into `DisplaySet` that `OHIF` uses for the visualization. `DisplaySetService` gets initialized at service startup time, but is then cleared in the `Mode.jsx`. During the initialization `SOPClassHandlerIds` of the `modes` gets registered with the `DisplaySetService`. +:::tip + +DisplaySet is a general set of entities and contains links to bunch of displayable objects (images, etc.) Some series might get split up into different displaySets e.g., MG might have mixed views in a single series, but users might want to have separate LCC, RCC, etc. for hanging protocol usage. A viewport renders a display set into a displayable object. + +imageSet is a particular implementation of image displays. +::: + + > Based on the instanceMetadata's `SOPClassHandlerId`, the correct module from the registered extensions is found by `OHIF` and its `getDisplaySetsFromSeries` runs to create a DisplaySet for the Series. Note that this is an ordered operation, and consumes the instances as it proceeds, with the first registered handlers being able to consume instances first. @@ -33,6 +41,7 @@ There are three events that get broadcasted in `DisplaySetService`: | DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache | | DISPLAY_SETS_CHANGED | Fires when a displayset is changed | | DISPLAY_SETS_REMOVED | Fires when a displayset is removed | +| DISPLAY_SET_SERIES_METADATA_INVALIDATED | Fires when a displayset's series metadata has been altered. An object payload for the event is sent with properties: `displaySetInstanceUID` - the UID of the display set affected; `invalidateData` - boolean indicating if data should be invalidated ## API @@ -60,3 +69,5 @@ Let's find out about the public API for `DisplaySetService`. - `deleteDisplaySet`: Deletes the displaySets from the displaySets cache - `addActiveDisplaySets`: Adds a new display set independently of the make operation. + +- `setDisplaySetMetadataInvalidated`: Fires the `DISPLAY_SET_SERIES_METADATA_INVALIDATED` event. diff --git a/platform/docs/docs/platform/services/data/HangingProtocolService.md b/platform/docs/docs/platform/services/data/HangingProtocolService.md index a30118f3ed5..f2e156b6336 100644 --- a/platform/docs/docs/platform/services/data/HangingProtocolService.md +++ b/platform/docs/docs/platform/services/data/HangingProtocolService.md @@ -45,7 +45,7 @@ export default function getHangingProtocolModule() { } ``` -Within the protocol itself, the structure is layed out as described in the HangingProtocol.ts +Within the protocol itself, the structure is laid out as described in the HangingProtocol.ts type definition, starting with `Protocol`. See the type definition for more details. ## Events @@ -71,7 +71,7 @@ the stage activate. The status values are: * enabled - meaning that the stage is fully applicable * passive - meaning that the stage can be applied, but might be missing details -* disabled - meaning that the study has insuffient information for this stage +* disabled - meaning that the study has insufficient information for this stage The default values for no `stageActivation` are to assume that `enabled` has `minViewports` of 1, and `passive` has `minViewports=0`. That is, enable the stage if at least one @@ -118,7 +118,7 @@ stageActivation: { stable as to exactly what this returns, as internal details can change. - `getState`: Returns the currently applied protocol ID, stage index and active study UID. - This information is storable/useable as state information to be used elsewhere. + This information is storable/usable as state information to be used elsewhere. - `getDefaultProtocol`: Returns the default protocol to apply. diff --git a/platform/docs/docs/platform/services/data/MeasurementService.md b/platform/docs/docs/platform/services/data/MeasurementService.md index 21bd4589f94..f4ecd51f62c 100644 --- a/platform/docs/docs/platform/services/data/MeasurementService.md +++ b/platform/docs/docs/platform/services/data/MeasurementService.md @@ -68,7 +68,7 @@ There are seven events that get publish in `MeasurementService`: - `toMeasurementSchema`: A function to get the `data` into the same shape as the source definition. -- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have +- `jumpToMeasurement(viewportId, id)`: calls the listeners who have subscribed to `JUMP_TO_MEASUREMENT`. ## Source / Mappers diff --git a/platform/docs/docs/platform/services/data/SegmentationService.md b/platform/docs/docs/platform/services/data/SegmentationService.md index 42e47c55300..bd99e12dee1 100644 --- a/platform/docs/docs/platform/services/data/SegmentationService.md +++ b/platform/docs/docs/platform/services/data/SegmentationService.md @@ -45,8 +45,8 @@ There are seven events that get publish in `MeasurementService`: ### Segment Behavior -- setSegmentLockedForSegmentation, removeSegment, addSegment, setSegmentLockedForSegmentation, setSegmentLabel, setActiveSegmentForSegmentation, -setSegmentRGBAColorForSegmentation +- setSegmentLocked, removeSegment, addSegment, setSegmentLocked, setSegmentLabel, setActiveSegment, +setSegmentRGBAColor ### Segmentation Configuration diff --git a/platform/docs/docs/platform/services/data/StateSyncService.md b/platform/docs/docs/platform/services/data/StateSyncService.md index 4e6964d17dc..6688c5eca82 100644 --- a/platform/docs/docs/platform/services/data/StateSyncService.md +++ b/platform/docs/docs/platform/services/data/StateSyncService.md @@ -47,7 +47,7 @@ grid store state as a modal state. ``` ### getState -The `getState` call returns an object containing all of the reigstered states, +The `getState` call returns an object containing all of the registered states, by id. The values can be read directly, but should not be modified. ### reduce diff --git a/platform/docs/docs/platform/services/data/index.md b/platform/docs/docs/platform/services/data/index.md index cdeb44c7730..6b92fdde1dd 100644 --- a/platform/docs/docs/platform/services/data/index.md +++ b/platform/docs/docs/platform/services/data/index.md @@ -17,9 +17,9 @@ We maintain the following non-ui Services: - [DicomMetadata Store](./../data/DicomMetadataStore.md) - [DisplaySet Service](./../data/DisplaySetService.md) - [Hanging Protocol Service](../data/HangingProtocolService.md) -- [Toolbar Service](../data/ToolBarService.md) +- [Toolbar Service](./ToolbarService.md) - [Measurement Service](../data/MeasurementService.md) -- [Customization Service](../data/customization-service.md) +- [Customization Service](./../ui/customization-service.md) - [State Sync Service](../data/StateSyncService.md) - [Panel Service](../data/PanelService.md) diff --git a/platform/docs/docs/platform/services/ui/customization-service.md b/platform/docs/docs/platform/services/ui/customization-service.md index 171548c20cf..7fc5a60ee30 100644 --- a/platform/docs/docs/platform/services/ui/customization-service.md +++ b/platform/docs/docs/platform/services/ui/customization-service.md @@ -43,7 +43,8 @@ automatically when the extension or mode is loaded. In the `value` of each customizations, you will define customization prototype(s). These customization prototype(s) can be considered like "Prototype" in Javascript. These can be used to extend the customization definitions from configurations. -Default cutomizations will be often used to define all the customization prototypes, +Default customizations will be often used to define all the customization prototypes, +Default customizations will be often used to define all the customization prototypes, as they will be loaded automatically along with the defining extension or mode. @@ -58,7 +59,6 @@ For example, the `@ohif/extension-default` extension defines, value: [ { id: 'ohif.overlayItem', - uiType: 'uiType', content: function (props) { if (this.condition && !this.condition(props)) return null; @@ -92,8 +92,9 @@ For example, the `@ohif/extension-default` extension defines, ], ``` -And this `ohif.overlayItem` object will be used as a prototype to define items -to be displayed on `CustomizableViewportOverlay`. See the next section. +And this `ohif.overlayItem` object will be used as a prototype (and template) to define items +to be displayed on `CustomizableViewportOverlay`. See how we use the `ohif.overlayItem` in +the example below. ## Configuring customizations @@ -119,13 +120,16 @@ window.config = { customizationService: { cornerstoneOverlayTopRight: { id: 'cornerstoneOverlayTopRight', - customizationType: 'ohif.cornerstoneOverlay', items: [ { id: 'PatientNameOverlay', - // Note the overlayItem as a parent type - this provides the - // rendering functionality to read the attribute and use the label. + // Note below that here we are using the customization prototype of + // `ohif.overlayItem` which was registered to the customization module in + // `ohif/extension-default` extension. customizationType: 'ohif.overlayItem', + // the following props are passed to the `ohif.overlayItem` prototype + // which is used to render the overlay item based on the label, color, + // conditions, etc. attribute: 'PatientName', label: 'PN:', title: 'Patient Name', @@ -157,20 +161,19 @@ The `customizationType` field is simply the id of another customization object. ### Mode Customizations Mode-specific customizations are no different from the global ones, -except that the mode customizations are cleared before the mode `onModeEnter` -is called, and they can have new values registered in the `onModeEnter` +except that the mode customizations are specific to one mode and +are not globally applied. Mode-specific customizations are also cleared +before the mode `onModeEnter` is called, and they can have new values registered in the `onModeEnter` -In the mode customization, the overlay is then further customized -with a bottom-right overlay, which extends the customizationService configuration. +Following on our example above to customize the overlay, we can now add a mode customization +with a bottom-right overlay. ```js // Import the type from the extension itself -import OverlayUICustomization from '@ohif/cornerstone-extension'; - +import OverlayUICustomization from "@ohif/cornerstone-extension"; // In the mode itself, customizations can be registered: -onModeEnter() { - ... +onModeEnter: { // Note how the object can be strongly typed const bottomRight: OverlayUICustomization = { id: 'cornerstoneOverlayBottomRight', @@ -178,10 +181,11 @@ onModeEnter() { customizationType: 'ohif.cornerstoneOverlay', // The cornerstoneOverlay definition requires an items list here. items: [ - // Custom definitions for hte context menu here. + // Custom definitions for the context menu here. ], }; customizationService.addModeCustomizations(bottomRight); +} ``` The mode customizations are retrieved via the `getModeCustomization` function, @@ -198,9 +202,17 @@ can then be used in a way defined by the extension provided that customization point. ```ts - cornerstoneOverlay = uiConfigurationService.getModeCustomization("cornerstoneOverlay", {customizationType: "ohif.cornerstoneOverlay", ...}); - const { component: overlayComponent, props} = uiConfigurationService.getComponent(cornerstoneOverlay); - return (); +const cornerstoneOverlay = customizationService.getModeCustomization( + "cornerstoneOverlay", + { customizationType: "ohif.cornerstoneOverlay" }, +); + +const { component: overlayComponent, props } = + customizationService.getComponent(cornerstoneOverlay); + +return ( + +); ``` This example shows fetching the default component to render this object. The @@ -210,8 +222,11 @@ example (this example comes from the context menu customizations as that one uses commands lists): ```ts - cornerstoneContextMenu = uiConfigurationService.get("cornerstoneContextMenu", defaultMenu); - commandsManager.run(cornerstoneContextMenu, extraProps); +cornerstoneContextMenu = customizationService.get( + "cornerstoneContextMenu", + defaultMenu, +); +commandsManager.run(cornerstoneContextMenu, extraProps); ``` ### Global Customizations @@ -285,7 +300,7 @@ uses it's own internal class names. * Name: 'class:StudyBrowser' * Attributes: ** `true` for the is active true text color -** `false` fo rhte is active false text color. +** `false` for the is active false text color. ** Values are button colors, from the Button class, eg default, white, black ## customRoutes @@ -339,7 +354,6 @@ window.config = { customizationService: { cornerstoneOverlayTopLeft: { id: 'cornerstoneOverlayTopLeft', - customizationType: 'ohif.cornerstoneOverlay', items: [ { id: 'WindowLevel', @@ -398,7 +412,6 @@ window.config = { }, cornerstoneOverlayTopRight: { id: 'cornerstoneOverlayTopRight', - customizationType: 'ohif.cornerstoneOverlay', items: [ { @@ -438,7 +451,6 @@ window.config = { }, cornerstoneOverlayBottomLeft: { id: 'cornerstoneOverlayBottomLeft', - customizationType: 'ohif.cornerstoneOverlay', items: [ { diff --git a/platform/docs/docs/platform/services/ui/ui-modal-service.md b/platform/docs/docs/platform/services/ui/ui-modal-service.md index cb36d7e6cf6..e78bc9c01bf 100644 --- a/platform/docs/docs/platform/services/ui/ui-modal-service.md +++ b/platform/docs/docs/platform/services/ui/ui-modal-service.md @@ -17,7 +17,7 @@ article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article]
- +
## Interface diff --git a/platform/docs/docs/platform/services/ui/ui-notification-service.md b/platform/docs/docs/platform/services/ui/ui-notification-service.md index 96185943482..815ac6d8d00 100644 --- a/platform/docs/docs/platform/services/ui/ui-notification-service.md +++ b/platform/docs/docs/platform/services/ui/ui-notification-service.md @@ -19,7 +19,7 @@ article: ["How To Design Notifications For Better UX"][ux-article]
- +
diff --git a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md index 7370428459d..b50e3e7e477 100644 --- a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md +++ b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md @@ -48,7 +48,7 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] ```js const DEFAULT_STATE = { - viewportIndex: null, + viewportId: null, message: undefined, type: 'info', // "error" | "warning" | "info" | "success" actions: undefined, // array of { type, text, value } diff --git a/platform/docs/docs/platform/services/ui/viewport-grid-service.md b/platform/docs/docs/platform/services/ui/viewport-grid-service.md index d523a7415a5..ee610a98c82 100644 --- a/platform/docs/docs/platform/services/ui/viewport-grid-service.md +++ b/platform/docs/docs/platform/services/ui/viewport-grid-service.md @@ -15,7 +15,7 @@ There are seven events that get publish in `ViewportGridService `: | Event | Description | | ----------------------------- | --------------------------------------------------| -| ACTIVE_VIEWPORT_INDEX_CHANGED | Fires the index of the active viewport is changed | +| ACTIVE_VIEWPORT_ID_CHANGED | Fires the Id of the active viewport is changed | | LAYOUT_CHANGED | Fires the layout is changed | | GRID_STATE_CHANGED | Fires when the entire grid state is changed | ## Interface @@ -25,13 +25,14 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] | API Member | Description | | --------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | +| `setActiveViewportId(viewportId)` | Sets the active viewport Id in the app | | `getState()` | Gets the states of the viewport (see below) | -| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | +| `setDisplaySetsForViewport({ viewportId, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | | `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | | `reset()` | Resets the default states | | `getNumViewportPanes()` | Gets the number of visible viewport panes | | `getLayoutOptionsFromState(gridState)` | Utility method that produces a `ViewportLayoutOptions` based on the passed in state| +| `getActiveViewportId()` | Returns the viewport Id of the active viewport in the grid| ## Implementations @@ -56,6 +57,6 @@ const DEFAULT_STATE = { * } */ ], - activeViewportIndex: 0, + activeViewportId: null, }; ``` diff --git a/platform/docs/docs/release-notes.md b/platform/docs/docs/release-notes.md index 2ab1ef1f2d0..146f567b1d6 100644 --- a/platform/docs/docs/release-notes.md +++ b/platform/docs/docs/release-notes.md @@ -5,34 +5,100 @@ sidebar_label: Release Notes # Release Notes -> New `OHIF-v3` architecture has made OHIF a general purpose extensible medical -> imaging **platform**, as opposed to a configurable viewer. -## What's new in `OHIF-v3` +## Current Release (master branch) -`OHIF-v3` is our second try for a React-based viewer, and is the third version -of our medical image web viewers from the start. The summary of changes includes: +### OHIF Viewer v3.6 - Official Version 3 Release (June 2023) +Check out the complete press announcement [here](https://ohif.org/newsletters/2023-06-08-ohif%20viewer%20v3%20official%20release%20&%20new%20nci%20funding--release). + +- Official OHIF v3 release: An important milestone achieved with OHIF v3 now at feature parity with v2 but with a more extensible and powerful framework. + +New Features: + +- DICOM Radiotherapy Structure Sets: Enhancement of DICOM RTSTRUCT rendering pipeline to better integrate with other segmentation types. +- Slide Microscopy: Slide microscopy code updated with the latest technologies from the DICOM Microscopy Library and SLIM Viewer. +- DICOM Uploader: New feature to easily upload DICOM files directly from the viewer to their PACS systems over DICOMWeb. +- Cornerstone DICOM Image Loader Migrated to TypeScript: Transition to the new TypeScript-based DICOM Image Loader library. +- Cornerstone3D 1.0 Release: Announcement of Cornerstone3D reaching version 1.0, indicating its readiness for production use. + +## Previous V3 Releases (on `v3-stable` branch, before merge to `master`) + +### OHIF Viewer v3.5 - Docker Build + +This update represents a minor release that is primarily focused on enhancing the development environment of the OHIF Viewer. It achieves this by integrating Docker build support, which is essential for streamlining the deployment process and ensuring consistent environments. Additionally, in an effort to optimize the development workflow, this release takes care of pushing changes to the master branch. Furthermore, it strategically splits the master branch from the release branch. This separation is crucial as it allows the developers to work more efficiently on the ongoing developments in the master branch, while simultaneously ensuring that the release branch remains stable and well-maintained. Such an approach underlines the commitment to both innovation and reliability. + + +### OHIF Viewer v3.4 - Segmentation Support (April 2023) +Check out the complete press announcement [here](https://ohif.org/newsletters/2023-04-03-new%20product%20features,%20grant%20updates%20and%20collaborations). + +- New Viewport State Preservation: Enhancements in state synchronization in OHIF Viewer for a seamless experience when switching between Multiplanar Reformatting (MPR) and other views. + +- Enhanced Hanging Protocol Matching: Improved hanging protocols for a faster, more user-friendly experience. + +- Customizable Context Menu: Expansion of context menu options allowing for greater customization and addition of sub-menus. + +- UI/UX Improvements: Revamped viewport header design and the addition of double-click functionality to maximize viewport. + +### OHIF Viewer v3.3 - Segmentation Support (November 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-11-21-ohif%20viewer:%20dicom%20segmentation%20support). + +- 3D Segmentation: Segmentations are natively loaded and rendered in 3D. The UI includes various interaction tools and rendering display preferences. Segmentation creation and editing tools are in development. + +- Fast and Optimized Multiplanar Reconstruction (MPR): The viewer now supports MPR visualization of volumes and segmentations, leading to significantly reduced memory footprint and improved performance. + +- New Collapsible Side Panels: The OHIF Viewer has redesigned side panels to be more compact and user friendly. + +- Context-aware Drag and Drop via Hanging Protocols: The viewer now allows a more seamless experience when dragging and dropping thumbnails. + +- New Tools: Two new tools have been added: Reference Lines and Stack Synchronizations. + + + +### OHIF Viewer v3.2 - Mode Gallery & TMTV Mode (August 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-08-18-mode%20gallery%20and%20tmtv%20mode). + +- New Total Metabolic Tumor Volume (TMTV) Workflow: This new mode includes high-performance rendering of volumetric data in ten distinct viewports, fusion of two series with adjustable colormaps, synchronization of the viewports for both camera and VOI settings, jump-to-click capability to synchronize navigation in all viewports, and support for exporting results. + +- OHIF Workflow Mode Gallery: This new feature is a one-stop shop for users to find, install, and use OHIF modes and share functionality with the community. The gallery is continuously updated with community-submitted modes. + + +### OHIF Viewer v3.1 - Cornerstone3D Integration (July 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-07-28-ohif%20&%20cornerstone3d%20integratione). + +- Cornerstone3D Integration: OHIF v3.1 has deprecated the legacy Cornerstone.js extension and replaced all functionality with Cornerstone3D. This includes updating the Measurement Tracking workflow mode. + +- GPU Accelerated 2D Rendering: The Cornerstone3D rendering engine now supports WebGL 2D textures for large images, increasing the interaction speed significantly compared to the legacy Cornerstone.js engine. + +- Upgraded Hanging Protocol Engine: The OHIF Hanging Protocol Engine has been updated for increased flexibility and ease of writing protocols. This includes the ability to position viewports in any non-grid layout and specify viewport configurations such as colormap, initial VOI, initial image to render, orientation, and more. + + +### OHIF Viewer v3.0 Public Beta Launch (September 2021) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2021-09-14-ohif%20update:%20v3%20public%20beta%20launch--release). + +- UI has been completely redesigned with modularity and workflow modes in mind. +- New UI components have been built with Tailwind CSS - Addition of workflow modes - Often, medical imaging use cases involves lots of specific workflows that - re-use functionalities. We have added the capability of workflow modes, that + reuse functionalities. We have added the capability of workflow modes, that enable people to customize user interface and configure application for specific workflow. - - The idea is to re-use the functionalities that extensions provide and create + - The idea is to reuse the functionalities that extensions provide and create a workflow. Brain segmentation workflow is different from prostate segmentation in UI for sure; however, they share the segmentation tools that can be re-used. - Our vision is that technical people focus of developing extensions which provides core functionalities, and experts to build modes by picking the appropriate functionalities from each extension. - - Migrated all the `cornerstone-core` and `cornerstone-tools` usage to the newly released `Cornerstone3D`. -* UI has been completely redesigned with modularity and workflow modes in mind. -* New UI components have been built with Tailwind CSS -* Redux store has been removed from the viewer in favour of services backed by - React's Context **API** -Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: + + + diff --git a/platform/docs/docs/resources.md b/platform/docs/docs/resources.md index 96d55e343af..9ff2e7a9d8c 100644 --- a/platform/docs/docs/resources.md +++ b/platform/docs/docs/resources.md @@ -10,18 +10,27 @@ conferences and "hackathons". In this page, we will provide the presentations and other resources that we have provided to the community in the past: ## 2023 + +### ITCR 2023 Conference | September 11-13, 2023 + +Dr. Gordon Harris presented an update on OHIF in [NCI Informatics Technology for Cancer Research Annual Meeting](https://www.itcr2023.org/). You can find the slides and poster here: +[[Slides]](https://docs.google.com/presentation/d/1R38s95db_yZj0WoYdlUbaWGZsWVb3H-3u_hXBZXiTaE/edit?usp=sharing)[[Poster]](https://ohif-assets.s3.us-east-2.amazonaws.com/presentations/OHIF-ITCR-2023-FINAL-PRINT.pdf) + + + + ### SIIM 2023 Tech Tools Webinar | April 12th, 2023 Free, Open Source Tools for Research: MONAI and OHIF Viewer [[Slides](https://docs.google.com/presentation/d/1afJ5Y9Tzukgn7eAbaO1oiCtN7XvIimFdmZP-HcOUofA/edit?usp=sharing)][[Video](https://www.youtube.com/watch?v=lo8J5w5jUJI)] -### [NA-MIC Project Week 38th 2023 - Remote] +### NA-MIC Project Week 38th 2023 - Remote We participated in the 38th Project Week with three projects around OHIF. [[Website](https://projectweek.na-mic.org/PW38_2023_GranCanaria/)] - PolySeg representations for OHIF Viewer ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_PolySeg/)) -- Cross study sychronizer for OHIF Crosshair ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_SyncCrosshair/)) +- Cross study synchronizer for OHIF Crosshair ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_SyncCrosshair/)) - DATSCAN Viewer implementation in OHIF ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_DATSCAN/)) @@ -43,7 +52,7 @@ The Imaging Network Ontario (ImNO) is an annual symposium that brings together medical imaging researchers and scientists from across Canada to share knowledge, ideas, and experiences. [[Slides]](https://docs.google.com/presentation/d/18XZDon4-Sitc2a70V5sFyhyUVZI_mIgfXHGtdxhZMjE/edit?usp=sharing) -[[Video]](https://vimeo.com/691134870/ad7d308a44) +[[Video]](https://vimeo.com/843234581/ad7d308a44) ### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) @@ -81,7 +90,7 @@ Healthcare Imaging with Cloud Healthcare API OHIF pitch for Informatics Technology for Cancer Research (ITCR) [[Slides]](https://docs.google.com/presentation/d/1MZXnZrVAnjmhVIWqC-aRSvJOoMMRLhLddACdCa1TybM/edit?usp=sharing) -[[Video]](https://vimeo.com/678769373/625bdb8793) +[[Video]](https://vimeo.com/843234613/625bdb8793) ## 2019 diff --git a/platform/docs/docs/user-guide/viewer/Language.md b/platform/docs/docs/user-guide/viewer/Language.md index 9032ecc5f62..de064876c12 100644 --- a/platform/docs/docs/user-guide/viewer/Language.md +++ b/platform/docs/docs/user-guide/viewer/Language.md @@ -18,5 +18,5 @@ Summary of language changing usage can be seen below: ## Overview Video
- +
diff --git a/platform/docs/docs/user-guide/viewer/measurement-panel.md b/platform/docs/docs/user-guide/viewer/measurement-panel.md index b760bf37e46..b4a728d1c7a 100644 --- a/platform/docs/docs/user-guide/viewer/measurement-panel.md +++ b/platform/docs/docs/user-guide/viewer/measurement-panel.md @@ -62,7 +62,7 @@ For instance, running the Viewer on a local DCM4CHEE:
- +
## Overview Video @@ -70,5 +70,5 @@ An overview of measurement drawing and exporting can be seen below:
- +
diff --git a/platform/docs/docs/user-guide/viewer/measurement-tracking.md b/platform/docs/docs/user-guide/viewer/measurement-tracking.md index a15f7e5dfb6..528ec78d6c2 100644 --- a/platform/docs/docs/user-guide/viewer/measurement-tracking.md +++ b/platform/docs/docs/user-guide/viewer/measurement-tracking.md @@ -45,7 +45,7 @@ Below, you can see different icons that appear for a tracked vs. untracked serie
- +
@@ -55,7 +55,7 @@ Below, you can see different icons that appear for a tracked vs. untracked serie
- +
@@ -84,7 +84,7 @@ The full workflow for saving measurements to SR and loading SR into the viewer i
- +


@@ -93,13 +93,13 @@ The full workflow for saving measurements to SR and loading SR into the viewer i
- +


- +
### Loading DICOM SR into an Already Tracked Series @@ -120,5 +120,5 @@ you cannot edit the DICOM SR measurement.
- +
diff --git a/platform/docs/docs/user-guide/viewer/toolbar.md b/platform/docs/docs/user-guide/viewer/toolbar.md index cb982ce25b8..1fb5527a06f 100644 --- a/platform/docs/docs/user-guide/viewer/toolbar.md +++ b/platform/docs/docs/user-guide/viewer/toolbar.md @@ -81,5 +81,5 @@ An overview of tool usage can been seen below:
- +
diff --git a/platform/docs/docs/user-guide/viewer/viewport.md b/platform/docs/docs/user-guide/viewer/viewport.md index 173728ca153..1bfc693cae1 100644 --- a/platform/docs/docs/user-guide/viewer/viewport.md +++ b/platform/docs/docs/user-guide/viewer/viewport.md @@ -35,5 +35,5 @@ An overview of viewport layout change, and manipulation can be seen below:
- +
diff --git a/platform/docs/docusaurus.config.js b/platform/docs/docusaurus.config.js index 9e6611c2ce1..bdcaa205b19 100644 --- a/platform/docs/docusaurus.config.js +++ b/platform/docs/docusaurus.config.js @@ -6,13 +6,10 @@ */ const path = require('path'); -const versions = require('./versions.json'); -const VersionsArchived = require('./versionsArchived.json'); -const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( - 0, - 5 -); +// read this text file +const fs = require('fs'); +const versions = fs.readFileSync('../../version.txt', 'utf8').split('\n'); // This probably only makes sense for the beta phase, temporary // function getNextBetaVersionName() { @@ -36,8 +33,7 @@ const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( const isDev = process.env.NODE_ENV === 'development'; -const isDeployPreview = - process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview'; +const isDeployPreview = process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview'; const baseUrl = process.env.BASE_URL || '/'; const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap'; @@ -78,14 +74,21 @@ module.exports = { // }, themes: ['@docusaurus/theme-live-codeblock'], plugins: [ + () => ({ + name: 'resolve-react', + configureWebpack() { + return { + resolve: { + alias: { + // assuming root node_modules is up from "./packages/ + react: path.resolve('../../node_modules/react'), + }, + }, + }; + }, + }), path.resolve(__dirname, './pluginOHIFWebpackConfig.js'), 'plugin-image-zoom', // 3rd party plugin for image click to pop - [ - '@docusaurus/plugin-google-gtag', - { - trackingID: 'UA-110573590-2', - }, - ], [ '@docusaurus/plugin-client-redirects', { @@ -93,7 +96,7 @@ module.exports = { redirects: [ { // we need this for https://cloud.google.com/healthcare/docs/how-tos/dicom-viewers - to: '/2.0/deployment/recipes/google-cloud-healthcare', + to: '/2.0-deprecated/deployment/recipes/google-cloud-healthcare', from: [ '/connecting-to-image-archives/google-cloud-healthcare', '/connecting-to-image-archives/google-cloud-healthcare.html', @@ -233,13 +236,17 @@ module.exports = { // : undefined, versions: { current: { - label: 'Latest', + label: `${versions} (Latest)`, }, }, }, theme: { customCss: [require.resolve('./src/css/custom.css')], }, + gtag: { + trackingID: 'G-DDBJFE34EG', + anonymizeIP: true, + }, }), ], ], @@ -260,13 +267,11 @@ module.exports = { disableSwitch: false, // respectPrefersColorScheme: true, }, - /* - announcementBar: { - id: 'supportus', - content: - '⭐️ If you like Docusaurus, give it a star on GitHub! ⭐️', - }, - */ + announcementBar: { + id: 'healthimaging', + content: + '🚀 AWS has announced the general availability of HealthImaging! Easily connect your OHIF to it. Learn more Here!! 🌟', + }, prism: { theme: require('prism-react-renderer/themes/github'), darkTheme: require('prism-react-renderer/themes/dracula'), @@ -285,14 +290,8 @@ module.exports = { }, items: [ { - to: 'https://ohif.org/get-started', - label: 'Get Started', - target: '_blank', - position: 'left', - }, - { - to: 'https://ohif.org/examples', - label: 'Examples', + href: 'https://ohif.org/showcase', + label: 'Showcase', target: '_blank', position: 'left', }, @@ -304,11 +303,17 @@ module.exports = { label: 'Docs', }, { - to: 'https://ohif.org/community', - label: 'Community', + href: 'https://ohif.org/collaborate', + label: 'Collaborate', target: '_blank', position: 'left', }, + { + to: '/migration-guide', + label: 'Migration Guides', + position: 'left', + className: 'new-badge', + }, { to: '/help', //activeBaseRegex: '(^/help$)|(/help)', @@ -320,21 +325,6 @@ module.exports = { position: 'right', dropdownActiveClassDisabled: true, dropdownItemsAfter: [ - { - type: 'html', - value: '', - }, - { - type: 'html', - className: 'dropdown-archived-versions', - value: 'Archived versions', - }, - ...ArchivedVersionsDropdownItems.map( - ([versionName, versionUrl]) => ({ - label: versionName, - href: versionUrl, - }) - ), { type: 'html', value: '', @@ -387,9 +377,17 @@ module.exports = { to: '/', }, { - label: 'Installation', + label: 'Getting Started', to: 'development/getting-started', }, + { + label: 'FAQ', + to: '/faq', + }, + { + label: 'Resources', + to: '/resources', + }, ], }, { @@ -397,7 +395,7 @@ module.exports = { items: [ { label: 'Discussion board', - to: 'https://community.ohif.org/', + href: 'https://community.ohif.org/', }, { label: 'Help', @@ -410,15 +408,15 @@ module.exports = { items: [ { label: 'Donate', - to: 'https://giving.massgeneral.org/ohif', + href: 'https://giving.massgeneral.org/ohif', }, { label: 'GitHub', - to: 'https://github.com/OHIF/Viewers', + href: 'https://github.com/OHIF/Viewers', }, { label: 'Twitter', - to: 'https://twitter.com/OHIFviewer', + href: 'https://twitter.com/OHIFviewer', }, ], }, diff --git a/platform/docs/netlify.toml b/platform/docs/netlify.toml index e83629343b0..ff82abc6677 100644 --- a/platform/docs/netlify.toml +++ b/platform/docs/netlify.toml @@ -16,7 +16,7 @@ [build.environment] # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" - NODE_VERSION = "16.14.0" + NODE_VERSION = "18.16.1" YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" diff --git a/platform/docs/package.json b/platform/docs/package.json index e513ad61dee..b30ec649c07 100644 --- a/platform/docs/package.json +++ b/platform/docs/package.json @@ -1,6 +1,6 @@ { "name": "ohif-docs", - "version": "3.6.0", + "version": "3.7.0-beta.108", "private": true, "workspaces": { "nohoist": [ diff --git a/platform/docs/pluginOHIFWebpackConfig.js b/platform/docs/pluginOHIFWebpackConfig.js index 9362c0163ad..484ede7c917 100644 --- a/platform/docs/pluginOHIFWebpackConfig.js +++ b/platform/docs/pluginOHIFWebpackConfig.js @@ -1,4 +1,4 @@ -module.exports = function(context, options) { +module.exports = function (context, options) { return { name: 'plugin-ohif-webpack-config', configureWebpack(config, isServer, utils) { diff --git a/platform/docs/sidebars.js b/platform/docs/sidebars.js index 981a73cd7a8..de26dde1e1a 100644 --- a/platform/docs/sidebars.js +++ b/platform/docs/sidebars.js @@ -11,7 +11,7 @@ module.exports = { // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], + tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], // But you can create a sidebar manually /* diff --git a/platform/docs/src/css/custom.css b/platform/docs/src/css/custom.css index 2c59a3495b9..ee583b616a7 100644 --- a/platform/docs/src/css/custom.css +++ b/platform/docs/src/css/custom.css @@ -36,6 +36,8 @@ --ifm-color-warning: #e9e489; --ifm-alert-color: #333333; --ohif-color-border: #7bb2ce; + --site-primary-hue-saturation: 167 68%; + --site-primary-hue-saturation-light: 167 56%; /* do we really need this extra one? */ } html[data-theme='dark'] .header-github-link:before { @@ -273,3 +275,29 @@ html[data-theme='dark'] .docusaurus-highlight-code-line { padding: 0 var(--ifm-pre-padding); border-left: 3px solid #ff000080; } + +.new-badge::after, +.deprecated-badge::after { + font-size: 11px; + @apply inline-flex items-center justify-center rounded-sm; + @apply ml-1.5 px-1 py-0; +} + +.new-badge::after { + content: ' (NEW)'; + @apply bg-red-300 text-red-500; + @apply dark:bg-blue-900 dark:text-blue-100; +} + +div[class^='announcementBar_'] { + --site-announcement-bar-stripe-color1: hsl(var(--site-primary-hue-saturation) 85%); + --site-announcement-bar-stripe-color2: hsl(var(--site-primary-hue-saturation) 95%); + background: repeating-linear-gradient( + 35deg, + var(--site-announcement-bar-stripe-color1), + var(--site-announcement-bar-stripe-color1) 20px, + var(--site-announcement-bar-stripe-color2) 10px, + var(--site-announcement-bar-stripe-color2) 40px + ); + font-weight: 700; +} diff --git a/platform/docs/src/pages/help.md b/platform/docs/src/pages/help.md index 1eccbf4afa4..3610b39879a 100644 --- a/platform/docs/src/pages/help.md +++ b/platform/docs/src/pages/help.md @@ -3,7 +3,7 @@ We all need a little help sometimes. Don't let a few roadblocks stand in the way of you building something awesome. -## Community Support +## Get Support If you're a developer looking to contribute code, documentation, or discussion; we are more than happy to help provide clarification and answer questions via @@ -19,17 +19,11 @@ Complex issues specific to your organization/situation are still okay to post, but they're less likely to receive a response. Unfortunately, we have limited resources and must be judicious with how we allocate them. If you find yourself in this situation and in need of assistance, it may be in your best interest to -persue paid support. +pursue paid support. -## Commercial Support +If you need additional help, please [reach out to us](https://ohif.org/get-support) to get more information on +how we can help you. -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. - - [gh-issues]: https://github.com/OHIF/Viewers/issues/ [google-group]: https://groups.google.com/forum/#!forum/cornerstone-platform diff --git a/platform/docs/src/pages/versions.md b/platform/docs/src/pages/versions.md index f94dc5bd65e..7f985e56fd3 100644 --- a/platform/docs/src/pages/versions.md +++ b/platform/docs/src/pages/versions.md @@ -5,50 +5,16 @@ with the latest software engineering practices, here we are listing the versions the OHIF platform that we are currently supporting, and the versions that have been deprecated. -## Product Version vs Package Version +## Product Version -There are two types of versions that we need to consider here: - -- Product Version: which is the end user visible version of the viewer -- Package Version: which is the underlying libraries/packages of the platform on npm. Currently we have three product versions: - Version 1 (deprecated): Built with Meteor as a full stack application. -- Version 2 (master branch): Front end image viewer built with React -- Version 3 (alpha release): Re-architected Version 2.0 to allow for a more modular and customizable viewer. - -As per package versioning, we follow semantic versioning which looks like *a.b.c* where: - -- *a* is for major breaking changes -- *b* is for new features -- *c* is for patches/bug fixes - -You can read more semantic versioning [here](https://semver.org/). - - -## Maintained Product Versions - -### Version 3.1 Cornerstone3D - -OHIF Version 3.1 is the next major upcoming release of the OHIF platform. It uses -the next generation of the cornerstone library, [Cornerstone 3D](https://github.com/cornerstonejs/cornerstone3D-beta). -We are in the process of performing a parity check between this version and the `master` -branch before we merge it into the master branch. You can read more about the -roadmap timelines [here](https://ohif.org/roadmap/). - -### Version 2.0: Master branch - -Currently, the master branch of OHIF is on Product Version 2.0. - -## Archived Versions - -### Version 3.0 Cornerstone Legacy - -Version 3.0 which uses [cornerstone-core](https://github.com/cornerstonejs/cornerstone) and -[cornerstone-tools](https://github.com/cornerstonejs/cornerstoneTools) for rendering and -manipulation/annotation of images. +- Version 2 (deprecated): Front end image viewer built with React +- Version 3.x-beta (master branch): With latest bug fixes and features but not yet released (released under beta) +- Version 3.x (release branch): Released version of the OHIF platform which is more stable and tested -### Version 1.0 Meteor -Deprecated in favor of Version 2.0. Built using full stack Meteor as a full stack application. +You can read more about the differences between the versions in the [development section](../../docs/development/getting-started#branches) of the documentation +to understand which version is more suitable for your use case. diff --git a/platform/docs/src/utils/getMockedStudies.js b/platform/docs/src/utils/getMockedStudies.js index 04868cf650d..e7d1b7dab72 100644 --- a/platform/docs/src/utils/getMockedStudies.js +++ b/platform/docs/src/utils/getMockedStudies.js @@ -9,8 +9,7 @@ const DEFAULT_MOCKED_STUDIES_LIMIT = 1000; * @returns {array} Study list */ const getMockedStudies = (items = 50) => { - const num = - items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items; + const num = items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items; return new Array(num).fill(studyListMock.studies[0]); }; diff --git a/platform/docs/tailwind.config.js b/platform/docs/tailwind.config.js index 3081fe2e228..04f67fcdcbb 100644 --- a/platform/docs/tailwind.config.js +++ b/platform/docs/tailwind.config.js @@ -179,32 +179,32 @@ module.exports = { }, spacing: { px: '1px', - '0': '0', - '1': '0.15rem', - '2': '0.5rem', - '3': '0.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '8': '2rem', - '10': '2.5rem', - '12': '3rem', - '14': '3.5rem', - '16': '4rem', - '18': '4.5rem', - '20': '5rem', - '24': '6rem', - '32': '8rem', - '40': '10rem', - '48': '12rem', - '56': '14rem', - '64': '16rem', - '72': '18rem', - '80': '20rem', - '88': '22rem', - '96': '24rem', - '104': '26rem', - '112': '28rem', + 0: '0', + 1: '0.15rem', + 2: '0.5rem', + 3: '0.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 8: '2rem', + 10: '2.5rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 18: '4.5rem', + 20: '5rem', + 24: '6rem', + 32: '8rem', + 40: '10rem', + 48: '12rem', + 56: '14rem', + 64: '16rem', + 72: '18rem', + 80: '20rem', + 88: '22rem', + 96: '24rem', + 104: '26rem', + 112: '28rem', '250px': '250px', }, backgroundColor: theme => theme('colors'), @@ -238,22 +238,18 @@ module.exports = { }, borderWidth: { DEFAULT: '1px', - '0': '0', - '2': '2px', - '4': '4px', - '8': '8px', + 0: '0', + 2: '2px', + 4: '4px', + 8: '8px', }, boxShadow: { xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - DEFAULT: - '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', - md: - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', - lg: - '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', - xl: - '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', @@ -273,19 +269,19 @@ module.exports = { current: 'currentColor', }, flex: { - '1': '1 1 0%', - '0.3': '0.3 0.3 0%', - '0.5': '0.5 0.5 0%', + 1: '1 1 0%', + 0.3: '0.3 0.3 0%', + 0.5: '0.5 0.5 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none', }, flexGrow: { - '0': '0', + 0: '0', DEFAULT: '1', }, flexShrink: { - '0': '0', + 0: '0', DEFAULT: '1', }, fontFamily: { @@ -306,14 +302,7 @@ module.exports = { '"Noto Color Emoji"', ], serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], - mono: [ - 'Menlo', - 'Monaco', - 'Consolas', - '"Liberation Mono"', - '"Courier New"', - 'monospace', - ], + mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'], }, fontSize: { xs: '0.65rem', @@ -346,7 +335,7 @@ module.exports = { }), inset: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', auto: 'auto', full: '100%', viewport: '0.5rem', @@ -368,14 +357,14 @@ module.exports = { normal: '1.5', relaxed: '1.625', loose: '2', - '3': '.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '7': '1.75rem', - '8': '2rem', - '9': '2.25rem', - '10': '2.5rem', + 3: '.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', }, listStyleType: { none: 'none', @@ -410,13 +399,13 @@ module.exports = { }), minHeight: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', full: '100%', screen: '100vh', }), minWidth: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', xs: '2rem', sm: '4rem', md: '6rem', @@ -436,44 +425,44 @@ module.exports = { top: 'top', }, opacity: { - '0': '0', - '5': '.5', - '10': '.10', - '15': '.15', - '20': '.20', - '25': '.25', - '30': '.30', - '35': '.35', - '40': '.40', - '45': '.45', - '50': '.50', - '55': '.55', - '60': '.60', - '65': '.65', - '70': '.70', - '75': '.75', - '80': '.80', - '85': '.85', - '90': '.90', - '95': '.95', - '100': '1', + 0: '0', + 5: '.5', + 10: '.10', + 15: '.15', + 20: '.20', + 25: '.25', + 30: '.30', + 35: '.35', + 40: '.40', + 45: '.45', + 50: '.50', + 55: '.55', + 60: '.60', + 65: '.65', + 70: '.70', + 75: '.75', + 80: '.80', + 85: '.85', + 90: '.90', + 95: '.95', + 100: '1', }, order: { first: '-9999', last: '9999', none: '0', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', }, padding: theme => theme('spacing'), placeholderColor: theme => theme('colors'), @@ -482,9 +471,9 @@ module.exports = { current: 'currentColor', }), strokeWidth: { - '0': '0', - '1': '1', - '2': '2', + 0: '0', + 1: '1', + 2: '2', }, textColor: theme => theme('colors'), width: theme => ({ @@ -545,28 +534,28 @@ module.exports = { }), zIndex: { auto: 'auto', - '0': '0', - '10': '10', - '20': '20', - '30': '30', - '40': '40', - '50': '50', + 0: '0', + 10: '10', + 20: '20', + 30: '30', + 40: '40', + 50: '50', }, gap: theme => theme('spacing'), gridTemplateColumns: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', - '7': 'repeat(7, minmax(0, 1fr))', - '8': 'repeat(8, minmax(0, 1fr))', - '9': 'repeat(9, minmax(0, 1fr))', - '10': 'repeat(10, minmax(0, 1fr))', - '11': 'repeat(11, minmax(0, 1fr))', - '12': 'repeat(12, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + 7: 'repeat(7, minmax(0, 1fr))', + 8: 'repeat(8, minmax(0, 1fr))', + 9: 'repeat(9, minmax(0, 1fr))', + 10: 'repeat(10, minmax(0, 1fr))', + 11: 'repeat(11, minmax(0, 1fr))', + 12: 'repeat(12, minmax(0, 1fr))', }, gridColumn: { auto: 'auto', @@ -585,44 +574,44 @@ module.exports = { }, gridColumnStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridColumnEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridTemplateRows: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', }, gridRow: { auto: 'auto', @@ -635,23 +624,23 @@ module.exports = { }, gridRowStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, gridRowEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, transformOrigin: { center: 'center', @@ -665,25 +654,25 @@ module.exports = { 'top-left': 'top left', }, scale: { - '0': '0', - '50': '.5', - '75': '.75', - '90': '.9', - '95': '.95', - '100': '1', - '105': '1.05', - '110': '1.1', - '125': '1.25', - '150': '1.5', + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', - '0': '0', - '45': '45deg', - '90': '90deg', - '180': '180deg', + 0: '0', + 45: '45deg', + 90: '90deg', + 180: '180deg', }, translate: (theme, { negative }) => ({ ...theme('spacing'), @@ -697,10 +686,10 @@ module.exports = { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', - '0': '0', - '3': '3deg', - '6': '6deg', - '12': '12deg', + 0: '0', + 3: '3deg', + 6: '6deg', + 12: '12deg', }, transitionProperty: { none: 'none', @@ -720,14 +709,14 @@ module.exports = { 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, transitionDuration: { - '75': '75ms', - '100': '100ms', - '150': '150ms', - '200': '200ms', - '300': '300ms', - '500': '500ms', - '700': '700ms', - '1000': '1000ms', + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', }, }, variants: { @@ -737,26 +726,12 @@ module.exports = { alignSelf: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], - backgroundColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], - borderColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], borderRadius: ['responsive', 'focus', 'first', 'last'], borderStyle: ['responsive', 'focus'], borderWidth: ['responsive', 'focus', 'first', 'last'], diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/add-a-logo-to-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-logo-to-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/add-a-logo-to-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-logo-to-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/add-a-tool-to-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-tool-to-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/add-a-tool-to-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-tool-to-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/Standalone-Installation-Instructions.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/Standalone-Installation-Instructions.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/Standalone-Installation-Instructions.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/Standalone-Installation-Instructions.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/environment-installations.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/environment-installations.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/environment-installations.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/environment-installations.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/usage.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/usage.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/usage.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/usage.md diff --git a/platform/docs/versioned_docs/version-1.0/README.md b/platform/docs/versioned_docs/version-1.0-deprecated/README.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/README.md rename to platform/docs/versioned_docs/version-1.0-deprecated/README.md diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Assessment_Progress.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Assessment_Progress.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Assessment_Progress.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Assessment_Progress.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Association_Dialog.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Association_Dialog.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Association_Dialog.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Association_Dialog.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Audit_Trails.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Audit_Trails.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Audit_Trails.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Audit_Trails.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CINE.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CINE.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CINE.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CINE.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Change_Password.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Change_Password.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Change_Password.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Change_Password.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CompareMode.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CompareMode.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CompareMode.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CompareMode.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Comparison.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Comparison.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Comparison.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Comparison.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Configuration_Menu.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Configuration_Menu.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Configuration_Menu.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Configuration_Menu.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Conformance_Check.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Conformance_Check.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Conformance_Check.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Conformance_Check.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Download.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Download.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Download.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Download.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Ellipse.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Ellipse.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Ellipse.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Ellipse.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipH.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipH.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipH.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipH.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipV.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipV.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipV.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipV.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Forgot_Password.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Forgot_Password.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Forgot_Password.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Forgot_Password.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Generate_Report.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Generate_Report.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Generate_Report.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Generate_Report.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_HUD_Panel.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_HUD_Panel.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_HUD_Panel.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_HUD_Panel.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Image_Viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Image_Viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Image_Viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Image_Viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Final.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Final.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Final.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Final.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Finish.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Finish.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Finish.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Finish.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Initial.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Initial.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Initial.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Initial.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Installation.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Installation.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Installation.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Installation.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_License_Aggrement.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_License_Aggrement.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_License_Aggrement.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_License_Aggrement.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Prerequisites.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Prerequisites.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Prerequisites.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Prerequisites.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Select_Location.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Select_Location.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Select_Location.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Select_Location.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Services.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Services.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Services.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Services.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Successful.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Successful.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Successful.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Successful.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Uninstall.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Uninstall.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Uninstall.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Uninstall.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Invert.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Invert.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Invert.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Invert.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Key_Timepoints.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Key_Timepoints.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Key_Timepoints.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Key_Timepoints.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Login.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Login.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Login.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Login.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Logout.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Logout.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Logout.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Logout.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Magnify.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Magnify.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Magnify.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Magnify.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Measurements.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Measurements.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Measurements.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Measurements.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Need_Account.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Need_Account.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Need_Account.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Need_Account.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Select_Location.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Select_Location.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Select_Location.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Select_Location.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Pan.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Pan.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Pan.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Pan.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Quick_Switch_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Quick_Switch_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Quick_Switch_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Quick_Switch_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Remove_Associate.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Remove_Associate.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Remove_Associate.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Remove_Associate.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Report_PDF.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Report_PDF.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Report_PDF.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Report_PDF.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Reset.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Reset.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Reset.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Reset.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Response_Criteria.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Response_Criteria.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Response_Criteria.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Response_Criteria.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Rotate_Right.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Rotate_Right.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Rotate_Right.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Rotate_Right.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save1.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save1.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save1.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save1.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save2.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save2.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save2.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save2.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Scroll.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Scroll.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Scroll.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Scroll.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Select_Associate.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Select_Associate.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Select_Associate.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Select_Associate.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Server_Info.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Server_Info.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Server_Info.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Server_Info.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Multiple.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Multiple.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Multiple.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Multiple.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Single.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Single.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Single.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Single.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Studies_Panel.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Studies_Panel.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Studies_Panel.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Studies_Panel.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StudyList.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StudyList.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StudyList.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StudyList.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_CR.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_CR.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_CR.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_CR.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Delete.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Delete.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Delete.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Delete.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Label.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Label.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Label.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Label.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Rename.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Rename.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Rename.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Rename.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_UN.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_UN.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_UN.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_UN.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Temp_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Temp_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Temp_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Temp_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Themes.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Themes.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Themes.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Themes.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Lesion.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Lesion.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Lesion.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Lesion.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Series_Details.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Series_Details.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Series_Details.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Series_Details.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL_Presets.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL_Presets.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL_Presets.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL_Presets.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Zoom.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Zoom.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Zoom.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Zoom.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/lesionTracker.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/lesionTracker.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/lesionTracker.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/lesionTracker.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/worklist.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/worklist.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/worklist.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/worklist.png diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dcm4chee-with-docker.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dcm4chee-with-docker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dcm4chee-with-docker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dcm4chee-with-docker.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dicomweb.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dicomweb.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dicomweb.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dicomweb.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dimse.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dimse.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dimse.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dimse.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/google-cloud-healthcare.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/google-cloud-healthcare.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/google-cloud-healthcare.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/options.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/options.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/options.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/options.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/orthanc-with-docker.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/orthanc-with-docker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/orthanc-with-docker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/orthanc-with-docker.md diff --git a/platform/docs/versioned_docs/version-1.0/contributing.md b/platform/docs/versioned_docs/version-1.0-deprecated/contributing.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/contributing.md rename to platform/docs/versioned_docs/version-1.0-deprecated/contributing.md diff --git a/platform/docs/versioned_docs/version-1.0/data/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/data/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/data/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/data/data-hierarchy.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/data-hierarchy.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/data-hierarchy.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/data-hierarchy.md diff --git a/platform/docs/versioned_docs/version-1.0/data/image-viewport.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/image-viewport.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/image-viewport.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/image-viewport.md diff --git a/platform/docs/versioned_docs/version-1.0/data/measurements-and-annotations.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/measurements-and-annotations.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/measurements-and-annotations.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/measurements-and-annotations.md diff --git a/platform/docs/versioned_docs/version-1.0/data/tool-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/tool-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/tool-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/tool-management.md diff --git a/platform/docs/versioned_docs/version-1.0/deployment/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/deployment/building-for-production.md b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/building-for-production.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/building-for-production.md rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/building-for-production.md diff --git a/platform/docs/versioned_docs/version-1.0/deployment/security.md b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/security.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/security.md rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/security.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/essentials/architecture.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/architecture.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/architecture.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/architecture.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/configuration.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/configuration.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/configuration.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/configuration.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/installation.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/installation.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/installation.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/installation.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md similarity index 96% rename from platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md index e172aba863d..a9fd4d26b7b 100644 --- a/platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md +++ b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md @@ -54,7 +54,7 @@ This package also stores Meteor components for the interactive lesion table used ### User (*ohif-user*) ### Basic Viewer Components (*ohif-viewerbase*) -This is the largest package in the repository. It holds a large number of re-usable Meteor components that are used to build both the OHIF Viewer and Lesion Tracker. +This is the largest package in the repository. It holds a large number of reusable Meteor components that are used to build both the OHIF Viewer and Lesion Tracker. ### WADO Proxy (*ohif-wadoproxy*) Proxy for CORS diff --git a/platform/docs/versioned_docs/version-1.0/essentials/troubleshooting.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/troubleshooting.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/troubleshooting.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/troubleshooting.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/lesion-tracker.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/lesion-tracker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/lesion-tracker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/lesion-tracker.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/ohif-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/ohif-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/ohif-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/ohif-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/standalone-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/standalone-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/standalone-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/standalone-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/faq/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/faq/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/faq/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/faq/general.md b/platform/docs/versioned_docs/version-1.0-deprecated/faq/general.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/faq/general.md rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/general.md diff --git a/platform/docs/versioned_docs/version-1.0/faq/technical.md b/platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md similarity index 80% rename from platform/docs/versioned_docs/version-1.0/faq/technical.md rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md index 2c5f5e81564..f7cc314f2aa 100644 --- a/platform/docs/versioned_docs/version-1.0/faq/technical.md +++ b/platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md @@ -5,4 +5,4 @@ At the time of project conception, Meteor was a simple way to begin using bleedi ## Do you have any plans to stop using Meteor? -We have considered migrating templates from Blaze (http://blazejs.org/) to React (https://reactjs.org/) or Vue (https://vuejs.org/), simply because these decouple the view layer from the remainder of the application. Blaze currently lacks [Stand-alone Support](http://blazejs.org/#Better-Stand-alone-Support) which means that our templates are not re-usable outside of a Meteor application. This is certainly a downside, but the resource cost to migrate every template is significant. +We have considered migrating templates from Blaze (http://blazejs.org/) to React (https://reactjs.org/) or Vue (https://vuejs.org/), simply because these decouple the view layer from the remainder of the application. Blaze currently lacks [Stand-alone Support](http://blazejs.org/#Better-Stand-alone-Support) which means that our templates are not reusable outside of a Meteor application. This is certainly a downside, but the resource cost to migrate every template is significant. diff --git a/platform/docs/versioned_docs/version-1.0/layout/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/layout/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/layout/hanging-protocols.md b/platform/docs/versioned_docs/version-1.0-deprecated/layout/hanging-protocols.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/hanging-protocols.md rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/hanging-protocols.md diff --git a/platform/docs/versioned_docs/version-1.0/layout/layout-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/layout/layout-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/layout-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/layout-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/audit-trail.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/audit-trail.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/audit-trail.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/audit-trail.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/installation-on-windows.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/installation-on-windows.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/installation-on-windows.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/installation-on-windows.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/manage-studies-in-orthanc.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/manage-studies-in-orthanc.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/manage-studies-in-orthanc.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/manage-studies-in-orthanc.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/server-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/server-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/server-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/server-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/study-and-timepoint-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/study-and-timepoint-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/study-and-timepoint-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/study-and-timepoint-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/user-accounts.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-accounts.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/user-accounts.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-accounts.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/user-preferences.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-preferences.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/user-preferences.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-preferences.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/using-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/using-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/using-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/using-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/packages/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/packages/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/packages/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/packages/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/packages/measurements.md b/platform/docs/versioned_docs/version-1.0-deprecated/packages/measurements.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/packages/measurements.md rename to platform/docs/versioned_docs/version-1.0-deprecated/packages/measurements.md diff --git a/platform/docs/versioned_docs/version-2.0/Architecture.md b/platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/Architecture.md rename to platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md index 8b0dcbcb1b8..d61e3ffa1e7 100644 --- a/platform/docs/versioned_docs/version-2.0/Architecture.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md @@ -59,7 +59,7 @@ This diagram is a conceptual illustration of how the Viewer is architected. 1. (optional) `extensions` can be registered with `@ohif/core`'s `ExtensionManager` -2. `@ohif/core` provides bussiness logic and a way for `@ohif/viewer` to access +2. `@ohif/core` provides business logic and a way for `@ohif/viewer` to access registered extensions 3. The `@ohif/viewer` composes and provides data to components from our component library (`@ohif/ui`) diff --git a/platform/docs/versioned_docs/version-2.0/README.md b/platform/docs/versioned_docs/version-2.0-deprecated/README.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/README.md rename to platform/docs/versioned_docs/version-2.0-deprecated/README.md diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/architecture-diagram similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/architecture-diagram diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/canny-full.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/canny-full.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/cloud.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/cloud.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/embedded-viewer-diagram similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/embedded-viewer-diagram diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/nginx-image-archive.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/nginx-image-archive.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/npm-logo-red.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/npm-logo-red.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/scope-of-project.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/scope-of-project.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/user-access-control-request-flow.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/user-access-control-request-flow.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_DEPLOY.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_DEPLOY.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_CHECKS.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_CHECKS.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_RELEASE.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_RELEASE.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/architecture-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/architecture-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/cornerstone-tools-link.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/cornerstone-tools-link.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/dialog-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/dialog-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/embedded-viewer-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/embedded-viewer-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-panel.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-panel.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar-nested.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar-nested.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-viewport.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-viewport.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/homePage.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/homePage.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/homePage.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/homePage.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/jwt-explained.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/jwt-explained.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-default-theme.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-default-theme.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-ohif-theme.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-ohif-theme.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/lesionTracker.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/lesionTracker.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/locizeSponsor.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/locizeSponsor.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/modal-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/modal-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/netlify-drop.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/netlify-drop.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/nginx-image-archive.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/nginx-image-archive.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/notification-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/notification-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/open-graph.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/open-graph.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/scope-of-project.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/scope-of-project.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/services.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/services.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/services.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/services.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/surge-deploy.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/surge-deploy.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/ui-services.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/ui-services.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/user-access-control-request-flow.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/user-access-control-request-flow.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/viewer.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/viewer.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/viewer.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/worklist.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/worklist.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/worklist.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/worklist.png diff --git a/platform/docs/versioned_docs/version-2.0/configuring/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/configuring/data-source.md b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/data-source.md rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md diff --git a/platform/docs/versioned_docs/version-2.0/configuring/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/index.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/deployment/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md index 995a480776b..5444026d734 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/index.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md @@ -99,7 +99,7 @@ support it yet, but it is gaining wider adoption. If you have an existing archive and intend to host the OHIF Viewer at the same domain name as your archive, then connecting the two is as simple as following -the steps layed out in our +the steps laid out in our [Configuration Essentials Guide](./../configuring/index.md). #### What if I don't have an imaging archive? @@ -273,7 +273,7 @@ You can find an example of this setup in our In general, we recommend using the "Authorization Code Flow" ( [see `response_type=code` here][code-flows]); however, the "Implicit Flow" ( [see -`response_type=token` here][code-flows]) can work if additonal precautions are +`response_type=token` here][code-flows]) can work if additional precautions are taken. If the flow you've chosen produces a JWT Token, it's validity can be used to secure access to your Image Archive as well. diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md index 7ba3bb64b89..1c633b79f53 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md @@ -75,7 +75,7 @@ directory. Our build process knows which configuration file to use based on the and registered extension's features, are configured using this file. The easiest way to apply your own configuration is to modify the `default.js` -file. For more advanced cofiguration options, check out our +file. For more advanced configuration options, check out our [configuration essentials guide](/configuring/index.md). ## Next Steps diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/embedded-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/embedded-viewer.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/google-cloud-healthcare.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/google-cloud-healthcare.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/google-cloud-healthcare.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md index eddb0ddea43..81d15d1b642 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md @@ -8,7 +8,7 @@ sidebar_position: 4 At a certain point, you may want others to have access to your instance of the OHIF Viewer and its medical imaging data. This post covers one of many potential -setups that accomplish that. Please note, noticably absent is user account +setups that accomplish that. Please note, noticeably absent is user account control. Do not use this recipe to host sensitive medical data on the open web. Depending @@ -21,12 +21,12 @@ that builds on the lessons learned here. Our two biggest hurdles when hosting our image archive and web client are: -- Risks related to exposing our PACS to the netowrk +- Risks related to exposing our PACS to the network - Cross-Origin Resource Sharing (CORS) requests ### Handling Web Requests -We mittigate our first issue by allowing [Nginx][nginx] to handle incoming web +We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web requests. Nginx is open source software for web serving, reverse proxying, caching, and more. It's designed for maximum performance and stability -- allowing us to more reliably serve content than Orthanc's built-in server can. diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md index de217f4c079..ebacc0ea26b 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md @@ -16,7 +16,7 @@ less product offerings. While not required, it can simplify things to host your Web Viewer alongside your image archive. Services with more robust product offerings, like `Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able -to accomodate this setup. +to accommodate this setup. _Drag-n-drop_ diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md index ea5757df5b0..65bab3d9b48 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md @@ -77,7 +77,7 @@ _Create Your First User_ - Email Verified: `ON` - Click `Save` - Click the `Credentials` Tab - - New Pasword: `test` + - New Password: `test` - Password Confirmation: `test` - Temporary: `OFF` - Click: `Reset Password` diff --git a/platform/docs/versioned_docs/version-2.0/development/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/development/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/development/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/development/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/development/continous-integration.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/development/continous-integration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md index fd3bbb45e6e..c0761867117 100644 --- a/platform/docs/versioned_docs/version-2.0/development/continous-integration.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md @@ -1,10 +1,10 @@ --- sidebar_position: 3 -title: Continous Integration +title: Continuous Integration --- -# Continous Integration (CI) +# Continuous Integration (CI) -This repository uses `CircleCI` and `Netlify` for continous integration. +This repository uses `CircleCI` and `Netlify` for continuous integration. ## Deploy Previews diff --git a/platform/docs/versioned_docs/version-2.0/development/contributing.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/contributing.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/development/contributing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/contributing.md diff --git a/platform/docs/versioned_docs/version-2.0/development/getting-started.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/development/getting-started.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md index 7ec30c99d9d..219bd1f6259 100644 --- a/platform/docs/versioned_docs/version-2.0/development/getting-started.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md @@ -14,7 +14,7 @@ we make to the OHIF Viewer, then follow these steps: - [Fork][fork-a-repo] the [OHIF/Viewers][ohif-viewers-repo] repository - [Create a local clone][clone-a-repo] of your fork - `git clone https://github.com/YOUR-USERNAME/Viewers` -- Add OHIF/Viewers as a [remote repository][add-remote-repo] labled `upstream` +- Add OHIF/Viewers as a [remote repository][add-remote-repo] labeled `upstream` - Navigate to the cloned project's directory - `git remote add upstream https://github.com/OHIF/Viewers.git` diff --git a/platform/docs/versioned_docs/version-2.0/development/testing.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/development/testing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md index 4ca92dcbd7a..c8c66bc4b36 100644 --- a/platform/docs/versioned_docs/version-2.0/development/testing.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md @@ -44,7 +44,7 @@ choice for asserting an element's border color. Modern tooling gives us this "for free". It can catch invalid regular expressions, unused variables, and guarantee we're calling methods/functions -with the expected paramater types. +with the expected parameter types. Example Tooling: diff --git a/platform/docs/versioned_docs/version-2.0/extensions/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/extensions/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md index 0f4c1222182..c739aa8cea8 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/index.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md @@ -56,7 +56,7 @@ export default { */ id: 'example-extension', - // Lifecyle + // Lifecycle preRegistration() { /* */ }, // Modules getCommandsModule() { /* */ }, diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/pre-registration.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/pre-registration.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md index 3f9e864a90e..dbbbcfdd011 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md @@ -65,7 +65,7 @@ myCommandName: { | Property | Type | Description | | --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | -| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. | +| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instantiation. | | `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | | `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | @@ -150,7 +150,7 @@ It is up to the consuming application to define what contexts are possible, and which ones are currently active. As extensions depend heavily on these, we will likely publish guidance around creating contexts, and ways to override extension defined contexts in the near future. If you would like to discuss potential -changes to how contexts work, please don't hesistate to createa new GitHub +changes to how contexts work, please don't hesitate to create new GitHub issue. [Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/panel.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/panel.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/sop-class-handler.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/sop-class-handler.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md index 87e7050a86e..7246713b511 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md @@ -64,7 +64,7 @@ The simplest definition has the following properties: | `label` | User/display friendly to show in UI | \* | | `icon` | A string name for an icon supported by the consuming application. | \* | | `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` | -| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` | +| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | | `commandOptions` | (optional) Options to pass the target `commandName` | \* | | `context` | (optional) Overrides module's `defaultContext` | Array of string context names | diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md similarity index 93% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md index 4291c99f383..00e244d2441 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md @@ -25,7 +25,7 @@ Each `ViewportComponent` will receive the following props: ```html ``` @@ -34,7 +34,7 @@ Each `ViewportComponent` will receive the following props: | --------------- | --------------- | --------------------------------- | | `children` | React.element[] | | | `viewportData` | object | `viewportSpecificData` (probably) | -| `viewportIndex` | number | | +| `viewportId` | string | | ### `@ohif/viewer` diff --git a/platform/docs/versioned_docs/version-2.0/faq/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/faq/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/_category_.json diff --git a/platform/docs/versioned_docs/version-3.0/platform/browser-support.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md similarity index 98% rename from platform/docs/versioned_docs/version-3.0/platform/browser-support.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md index f717432aedc..daa83091636 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/browser-support.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Browser Support diff --git a/platform/docs/versioned_docs/version-2.0/faq/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/index.md diff --git a/platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/pwa-vs-packaged.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/pwa-vs-packaged.md diff --git a/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md similarity index 95% rename from platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md index 4ac68daa60e..b61f73d804e 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Scope of Project @@ -21,7 +21,7 @@ data source. To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. These can be delivered to your end users however you would like: -- From the local network +- From the local networok - From a remote web server - From a CDN (content delivery network) - From a service-worker's cache @@ -38,7 +38,7 @@ data; only the configuration necessary to interface with one or more of these many data sources. The OHIF Viewer's scope **DOES** include configuration and support for services that are protected with OpenID-Connect. -In an effort to aid our users and contributors, we attempt to provide several +In an effort to aide our users and contributors, we attempt to provide several [deployment and hosting recipes](./deployment/index.md) as potential starting points. These are not meant to be rock solid, production ready, solutions; like most recipes, they should be augmented to best fit you and your organization's @@ -55,7 +55,7 @@ the OHIF Viewer as a desktop application. _Does the OHIF Viewer work with the local filesystem?_ -It is possible to accomplish this through extensions; however, for a user +It is possible to accomplish this through extensions; however, for an user experience that accommodates a large number of studies, you would likely need to package the OHIF Viewer as an [Electron app][electron]. diff --git a/platform/docs/versioned_docs/version-2.0/help.md b/platform/docs/versioned_docs/version-2.0-deprecated/help.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/help.md rename to platform/docs/versioned_docs/version-2.0-deprecated/help.md index 1eccbf4afa4..d9acefcbf12 100644 --- a/platform/docs/versioned_docs/version-2.0/help.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/help.md @@ -19,7 +19,7 @@ Complex issues specific to your organization/situation are still okay to post, but they're less likely to receive a response. Unfortunately, we have limited resources and must be judicious with how we allocate them. If you find yourself in this situation and in need of assistance, it may be in your best interest to -persue paid support. +pursue paid support. ## Commercial Support diff --git a/platform/docs/versioned_docs/version-2.0/our-process.md b/platform/docs/versioned_docs/version-2.0-deprecated/our-process.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/our-process.md rename to platform/docs/versioned_docs/version-2.0-deprecated/our-process.md index 1e1934a4dcb..c6d7e51c336 100644 --- a/platform/docs/versioned_docs/version-2.0/our-process.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/our-process.md @@ -100,7 +100,7 @@ quality and test coverage must not changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video recordings of end-to-end tests are stored for later review. -[You can read more about our continous integration efforts here](/development/continous-integration.md) +[You can read more about our continuous integration efforts here](/development/continuous-integration.md) ## Releases diff --git a/platform/docs/versioned_docs/version-2.0/services/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/services/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/services/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/services/default.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/default.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/default.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/default.md diff --git a/platform/docs/versioned_docs/version-2.0/services/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/index.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/index.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-dialog-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-dialog-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-dialog-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-dialog-service.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-modal-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-modal-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-modal-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-modal-service.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-notification-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-notification-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-notification-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-notification-service.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/viewer/configuration.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/configuration.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/configuration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/configuration.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/environment-variables.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/environment-variables.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/environment-variables.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/environment-variables.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/internationalization.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/viewer/internationalization.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md index 6087b05579e..aa1136b3454 100644 --- a/platform/docs/versioned_docs/version-2.0/viewer/internationalization.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md @@ -93,7 +93,7 @@ export default withTranslation('MyNameSpace')(MyComponent); > Important: if you are using React outside the OHIF Viewer, check the > [I18nextProvider](#using-outside-of-ohif-viewer) section, `withTranslation` -> HOC doesnt works without a I18nextProvider +> HOC doesn't works without a I18nextProvider #### Using Hooks @@ -187,7 +187,7 @@ the following file tree: index.js | UK |-- Buttons.js - indes.js + index.js | US |-- Buttons.js index.js diff --git a/platform/docs/versioned_docs/version-2.0/viewer/themeing.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/themeing.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/themeing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/themeing.md diff --git a/platform/docs/versioned_docs/version-2.0/faq/browser-support.md b/platform/docs/versioned_docs/version-2.0/faq/browser-support.md deleted file mode 100644 index 8d664095a64..00000000000 --- a/platform/docs/versioned_docs/version-2.0/faq/browser-support.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -sidebar_position: 3 ---- -# Browser Support - -The browsers that we support are specified in the `.browserlistrc` file located -in the `platform/viewer` project. While we leverage the latest language features -when writing code, we rely on `babel` to _transpile_ our code so that it can run -in the browsers that we support. - -## In Practice - -The OHIF Viewer is capable of _running_ on: - -- IE 11 -- FireFox -- Chrome -- Safari -- Edge - -However, we do not have the resources to adequately test and maintain bug free -functionality across all of these. In order to push web based medical imaging -forward, we focus our development efforts on recent version of modern evergreen -browsers. - -Our support of older browsers equates to our willingness to review PRs for bug -fixes, and target their minimum JS support whenever possible. - -### Polyfills - -> A polyfill, or polyfiller, is a piece of code (or plugin) that provides the -> technology that you, the developer, expect the browser to provide natively. - -An example of a polyfill is that you expect `Array.prototype.filter` to exist, -but for some reason, the browser that's being used has not implemented that -language feature yet. Our earlier transpilation will rectify _syntax_ -discrepencies, but unimplemented features require a "temporary" implementation. -That's where polyfills step in. - -You can utilize a service like [polyfill.io](https://polyfill.io/v3/) to -auto-detect and apply polyfills as needed, or you can update the PWA build to -include polyfill's in your bundle by incorporating [core-js][core-js] - - - - -[core-js]: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md - diff --git a/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md b/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md deleted file mode 100644 index 05f97c455de..00000000000 --- a/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -sidebar_position: 2 ---- -# Scope of Project - -The OHIF Viewer is a web based medical imaging viewer. This allows it to be used -on almost any device, anywhere. The OHIF Viewer is what is commonly reffered to -as a ["Dumb Client"][simplicable] - -> A dumb client is software that fully depends on a connection to a server or -> cloud service for its functionality. Without a network connection, the -> software offers nothing useful. - [simplicable.com][simplicable] - -While the Viewer persists some data, it's scope is limited to caching things -like user preferences and previous query paramaters. Because of this, the Viewer -has been built to be highly configurable to work with almost any web accessible -data source. - -![scope-of-project diagram](./../assets/img/scope-of-project.png) - -To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. -These can be delivered to your end users however you would like: - -- From the local networok -- From a remote web server -- From a CDN (content delivery network) -- From a service-worker's cache -- etc. - -These "static asset" files are referred to collectively as a "Progressive Web -Application" (PWA), and have the same capabilities and limitations that all PWAs -have. - -All studies, series, images, imageframes, metadata, and the images themselves -must come from an external source. There are many, many ways to provide this -information, the OHIF Viewer's scope **DOES NOT** encompass providing _any_ -data; only the configuration necessary to interface with one or more of these -many data sources. The OHIF Viewer's scope **DOES** include configuration and -support for services that are protected with OpenID-Connect. - -In an effort to aide our users and contributors, we attempt to provide several -[deployment and hosting recipes](./deployment/index.md) as potential starting -points. These are not meant to be rock solid, production ready, solutions; like -most recipes, they should be augmented to best fit you and your organization's -taste, preferences, etc. - -## FAQ - -_Am I able to cache studies for offline viewing?_ - -Not currently. A web page's offline cache capabilities are limited and somewhat -volatile (mostly imposed at the browser vendor level). For more robust offline -caching, you may want to consider a server on the local network, or packaging -the OHIF Viewer as a desktop application. - -_Does the OHIF Viewer work with the local filesystem?_ - -It is possible to accomplish this through extensions; however, for an user -experience that accomodates a large number of studies, you would likely need to -package the OHIF Viewer as an [Electron app][electron]. - - - - -[simplicable]: https://simplicable.com/new/dumb-client -[electron]: https://electronjs.org/ - diff --git a/platform/docs/versioned_docs/version-3.0/README.md b/platform/docs/versioned_docs/version-3.0/README.md deleted file mode 100644 index fc840c84fa3..00000000000 --- a/platform/docs/versioned_docs/version-3.0/README.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: Introduction -slug: / -sidebar_position: 1 ---- - -The [Open Health Imaging Foundation][ohif-org] (OHIF) Viewer is an open source, -web-based, medical imaging platform. It aims to provide a core framework for -building complex imaging applications. - -Key features: - -- Designed to load large radiology studies as quickly as possible. Retrieves - metadata ahead of time and streams in imaging pixel data as needed. -- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding, - rendering, and annotating medical images. -- Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web]. - Offers a Data Source API for communicating with archives over proprietary API - formats. -- Provides a plugin framework for creating task-based workflow modes which can - re-use core functionality. -- Beautiful user interface (UI) designed with extensibility in mind. UI - components available in a reusable component library built with React.js and - Tailwind CSS - -![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.png) - - - -## Where to next? - -The Open Health Imaging Foundation intends to provide an imaging viewer -framework which can be easily extended for specific uses. If you find yourself -unable to extend the viewer for your purposes, please reach out via our [GitHub -issues][gh-issues]. We are actively seeking feedback on ways to improve our -integration and extension points. - -Check out these helpful links: - -- Ready to dive into some code? Check out our - [Getting Started Guide](./development/getting-started.md). -- We're an active, vibrant community. - [Learn how you can be more involved.](./development/contributing.md) -- Feeling lost? Read our [help page](/help). - -## Citing OHIF - -To cite 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) - -This article is freely available on Pubmed Central: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/ - - -or, for Lesion Tracker of OHIF 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) - -This article is freely available on Pubmed Central. -https://pubmed.ncbi.nlm.nih.gov/29092955/ - - -**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. - -## License - -MIT © [OHIF](https://github.com/OHIF) - -  - - - - -[ohif-org]: https://www.ohif.org -[ohif-demo]: http://viewer.ohif.org/ -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[gh-issues]: https://github.com/OHIF/Viewers/issues - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram deleted file mode 100644 index bbf6cf58b7a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig deleted file mode 100644 index 8756e9f7951..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg deleted file mode 100644 index ad04389c61a..00000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram deleted file mode 100644 index 182ad232399..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig deleted file mode 100644 index 460ae95dd35..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg deleted file mode 100644 index 8e4aac5d237..00000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig deleted file mode 100644 index 5eb82e561df..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig deleted file mode 100644 index 8982a8fedd5..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png deleted file mode 100644 index 7f28ceb4f30..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png deleted file mode 100644 index 4a58a1826a4..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png b/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png deleted file mode 100644 index fc477adf66d..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png deleted file mode 100644 index 3e562a7971d..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png deleted file mode 100644 index f9c4a568b6c..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png deleted file mode 100644 index 54b0aa39fbb..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png deleted file mode 100644 index f3c2a806973..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png deleted file mode 100644 index bb4955e97fb..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png deleted file mode 100644 index 6f1a16280db..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png deleted file mode 100644 index 40b511309b8..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png deleted file mode 100644 index b15713b3985..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png deleted file mode 100644 index 68ea6dc01a7..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png deleted file mode 100644 index 7b0375dcc39..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif deleted file mode 100644 index 22fde7a7f10..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png deleted file mode 100644 index a68264936a8..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png deleted file mode 100644 index 3ca4e26b006..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png b/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png deleted file mode 100644 index ea3c9ac9794..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png deleted file mode 100644 index 2d77dafe7b5..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png deleted file mode 100644 index 8eed743e2ed..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png b/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png deleted file mode 100644 index ad33ebe8865..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png deleted file mode 100644 index 49b3a4173f3..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png deleted file mode 100644 index 89ccc3e44af..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png deleted file mode 100644 index 426cb7ab855..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png deleted file mode 100644 index f26509a16fa..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png deleted file mode 100644 index 0ea77f9655f..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png deleted file mode 100644 index ad060f262a6..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg deleted file mode 100644 index 1139aa2c760..00000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg +++ /dev/null @@ -1,187 +0,0 @@ - - - - Custom Preset 2 Copy - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png deleted file mode 100644 index 3c6ba7d5316..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png deleted file mode 100644 index cafeae72cb2..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png deleted file mode 100644 index 12d21a0a7ab..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png deleted file mode 100644 index 90758691b08..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png deleted file mode 100644 index 9d46fd3192d..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png deleted file mode 100644 index d4bd71bb6c1..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png deleted file mode 100644 index f931818c374..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png deleted file mode 100644 index d855edbca75..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png deleted file mode 100644 index 9442411b5e0..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png deleted file mode 100644 index bd75479652a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png deleted file mode 100644 index a992f0169b2..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png deleted file mode 100644 index 5b881abdfd8..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/overview.png b/platform/docs/versioned_docs/version-3.0/assets/img/overview.png deleted file mode 100644 index d504f5b141e..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/overview.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png deleted file mode 100644 index fdbb5588489..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png deleted file mode 100644 index 83f9e19198a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png deleted file mode 100644 index b1e4c535131..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png deleted file mode 100644 index 7aeca269257..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png deleted file mode 100644 index 6daac8bee38..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png deleted file mode 100644 index e5251edd2d9..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png deleted file mode 100644 index 900419a8d1a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png deleted file mode 100644 index 34c3bf1ffe2..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services.png b/platform/docs/versioned_docs/version-3.0/assets/img/services.png deleted file mode 100644 index 569c046c0df..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif deleted file mode 100644 index 545f0686358..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png deleted file mode 100644 index 465c2a91e36..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png deleted file mode 100644 index 0c44ca962d1..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png deleted file mode 100644 index c63d1722ba5..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png deleted file mode 100644 index 57536695f63..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png deleted file mode 100644 index 8190b5f96e4..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png deleted file mode 100644 index 1a85837f38d..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png deleted file mode 100644 index 00acfcaec2a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png deleted file mode 100644 index d61b36d7e87..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png deleted file mode 100644 index d5b5959fa2e..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png deleted file mode 100644 index 988e56d4b03..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png deleted file mode 100644 index c62fde75cf5..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif b/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif deleted file mode 100644 index 599964e2444..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png deleted file mode 100644 index dd530637775..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png deleted file mode 100644 index 573c835038e..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png deleted file mode 100644 index 7621c4fedc7..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png deleted file mode 100644 index 389aa31dd16..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png deleted file mode 100644 index 1ce61742d97..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png deleted file mode 100644 index 5e2b29c4ed1..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png deleted file mode 100644 index 05d0c4ba7d8..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png deleted file mode 100644 index 4d589599ef8..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png deleted file mode 100644 index b082eedea24..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png deleted file mode 100644 index 42db7c2f780..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png deleted file mode 100644 index e5a5aad16af..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png deleted file mode 100644 index bf878bc5919..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png deleted file mode 100644 index ca7eef55c15..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png deleted file mode 100644 index 15632fd0a21..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png deleted file mode 100644 index 5a240146868..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png deleted file mode 100644 index d39c9cbe071..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png deleted file mode 100644 index 71110885db7..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png deleted file mode 100644 index 1ef3e76da20..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png deleted file mode 100644 index 2cb7fd7f0af..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png deleted file mode 100644 index fc36c58c0c1..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png deleted file mode 100644 index c79dce13ddd..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png deleted file mode 100644 index 58af0ad27e2..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png deleted file mode 100644 index 05423370c7a..00000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/configuration/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/_category_.json deleted file mode 100644 index 0aea1748dc4..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Configuration", - "position": 4 -} diff --git a/platform/docs/versioned_docs/version-3.0/configuration/configuration.md b/platform/docs/versioned_docs/version-3.0/configuration/configuration.md deleted file mode 100644 index cea7ce6fabd..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/configuration.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Configuration ---- - -# Viewer: Configuration - -The OHIF Viewer Platform strives to be highly configurable and extensible. This -makes it easier for our community members to keep their "secret sauce" private, -and incentives contributions back to the platform. The `@ohif/viewer` project of -the platform is the lynchpin that combines everything to create our application. - -There are two configuration mechanisms, one for run-time configuration, allowing -for changes to be decided based on including different configuration files as -specified by the 'theme' URL parameters. This mechanism is intended for -modifications of data exposed as configurable items by the existing code. See -sections below on configuring this type of value. - -The other mechanism is the code-configuration mechanism that specifies load -time configuration. This is intended to load things that require code level -changes to OHIF such as adding a new viewer configuration. This is also used -for base definitions that are shared site-wide such as the data sources. This -was the original configuration mechanism provided, and some of the configurations -specified there are better suited to the run time loading, but are currently -left alone as there hasn't been time to move them. - -We maintain a number of common viewer application configurations at -[`/platform/viewer/public/configs`][config-dir]. - -You can take a look at how to use different configs in the -[Environment Variables](../platform/environment-variables) - -```js title="/platform/viewer/public/configs" -window.config = { - routerBasename: '/', - /** - * "White Labeling" is used to change the branding, look, and feel of the OHIF - * Viewer. These settings, and the color variables that are used by our components, - * are the easiest way to rebrand the application. - * - * More extensive changes are made possible through swapping out the UI library, - * Viewer project, or extensions. - */ - whiteLabeling: { - /** ... **/ - }, - httpErrorHandler: { - /** coming soon **/ - }, - extensions: [], - showStudyList: true, - filterQueryParam: false, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], -}; -``` - - - - - -[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config - diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json deleted file mode 100644 index fe1e3e8595e..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Data Sources", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md deleted file mode 100644 index 08cbac7522f..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: DICOM JSON ---- - -# DICOM JSON - -You can launch the OHIF Viewer with a JSON file which points to a DICOMWeb -server as well as a list of study and series instance UIDs along with metadata. - -An example would look like - -`https://viewer.ohif.org/viewer/dicomjson?url=https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` - -As you can see the url to the location of the JSON file is passed in the query -after the `dicomjson` string, which is -`https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` (this -json file has been generated by OHIF team and stored in an amazon s3 bucket for -the purpose of the guide). - -## DICOM JSON sample - -Here we are using the LIDC-IDRI-0001 case which is a sample of the LIDC-IDRI -dataset. Let's have a look at the JSON file: - -### Metadata - -JSON file stores the metadata for the study level, series level and instance -level. A JSON launch file should follow the same structure as the one below. - -Note that at the instance level metadata we are storing both the `metadata` and -also the `url` for the dicom file on the dicom server. In this case we are -referring to -`dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm` -which is stored in another directory in our s3. (You can actually try -downloading the dicom file by opening the url in your browser). - -```json -{ - "studies": [ - // first study metadata - { - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "StudyDate": "20000101", - "StudyTime": "", - "PatientName": "", - "PatientID": "LIDC-IDRI-0001", - "AccessionNumber": "", - "PatientAge": "", - "PatientSex": "", - "series": [ - // first series metadata - { - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "SeriesNumber": 3000566, - "Modality": "CT", - "SliceThickness": 2.5, - "instances": [ - // first instance metadata - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 1, - "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.703125, 0.703125], - "HighBit": 15, - "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], - "ImagePositionPatient": [-166, -171.699997, -10], - "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", - "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], - "Modality": "CT", - "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.262721256650280657946440242654", - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "WindowCenter": -600, - "WindowWidth": 1600, - "SeriesDate": "20000101" - }, - "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm" - }, - // second instance metadata - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 2, - "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.703125, 0.703125], - "HighBit": 15, - "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], - "ImagePositionPatient": [-166, -171.699997, -12.5], - "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", - "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], - "Modality": "CT", - "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.512235483218154065970649917292", - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "WindowCenter": -600, - "WindowWidth": 1600, - "SeriesDate": "20000101" - }, - "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm" - } - // ..... other instances metadata - ] - } - // ... other series metadata - ], - "NumInstances": 133, - "Modalities": "CT" - } - // second study metadata - ] -} -``` - -![](../../assets/img/dicom-json.png) - -### Local Demo - -You can run OHIF with a JSON data source against you local datasets (given that -their JSON metadata is extracted). - -First you need to put the JSON file and the folder containing the dicom files -inside your `public` folder. Since files are served from your local server the -`url` for the JSON file will be `http://localhost:3000/LIDC-IDRI-0001.json` and -the dicom files will be -`dicomweb:http://localhost:3000/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm`. - -After `yarn install` and running `yarn dev` and opening the browser at -`http://localhost:3000/viewer/dicomjson?url=http://localhost:3000/LIDC-IDRI-0001.json` -will display the viewer. - -Download JSON file from -[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) - -Sample DICOM files can be downloaded from -[TCIA](https://wiki.cancerimagingarchive.net/display/Public/LIDC-IDRI) or -directly from -[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) - -Your public folder should look like this: - -![](../../assets/img/dicom-json-public.png) diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md deleted file mode 100644 index 83fbc1750bf..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: DICOMweb ---- - -# DICOMweb - -## Set up a local DICOM server - -ATTENTION! Already have a remote or local server? Skip to the -[configuration section](#configuration-learn-more) below. - -While the OHIF Viewer can work with any data source, the easiest to configure -are the ones that follow the [DICOMWeb][dicom-web] spec. - -1. Choose and install an Image Archive -2. Upload data to your archive (e.g. with DCMTK's [storescu][storescu] or your - archive's web interface) -3. Keep the server running - -For our purposes, we will be using `Orthanc`, but you can see a list of -[other Open Source options](#open-source-dicom-image-archives) below. - -### Requirements - -- Docker - - [Docker for Mac](https://docs.docker.com/docker-for-mac/) - - [Docker for Windows (recommended)](https://docs.docker.com/docker-for-windows/) - - [Docker Toolbox for Windows](https://docs.docker.com/toolbox/toolbox_install_windows/) - -_Not sure if you have `docker` installed already? Try running `docker --version` -in command prompt or terminal_ - -> If you are using `Docker Toolbox` you need to change the _PROXY_DOMAIN_ -> parameter in _platform/viewer/package.json_ to http://192.168.99.100:8042 or -> the ip docker-machine ip throws. This is the value [`WebPack`][webpack-proxy] -> uses to proxy requests - -## Open Source DICOM Image Archives - -There are a lot of options available to you to use as a local DICOM server. Here -are some of the more popular ones: - -| Archive | Installation | -| --------------------------------------------- | ---------------------------------- | -| [DCM4CHEE Archive 5.x][dcm4chee] | [W/ Docker][dcm4chee-docker] | -| [Orthanc][orthanc] | [W/ Docker][orthanc-docker] | -| [DICOMcloud][dicomcloud] (**DICOM Web only**) | [Installation][dicomcloud-install] | -| [OsiriX][osirix] (**Mac OSX only**) | Desktop Client | -| [Horos][horos] (**Mac OSX only**) | Desktop Client | - -_Feel free to make a Pull Request if you want to add to this list._ - -Below, we will focus on `DCM4CHEE` and `Orthanc` usage: - -### Running Orthanc - -_Start Orthanc:_ - -```bash -# Runs orthanc so long as window remains open -yarn run orthanc:up -``` - -_Upload your first Study:_ - -1. Navigate to - [Orthanc's web interface](http://localhost:8042/app/explorer.html) at - `http://localhost:8042/app/explorer.html` in a web browser. -2. In the top right corner, click "Upload" -3. Click "Select files to upload..." and select one or more DICOM files -4. Click "Start the upload" - -#### Orthanc: Learn More - -You can see the `docker-compose.yml` file this command runs at -[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on -Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. - -#### Connecting to Orthanc - -Now that we have a local Orthanc instance up and running, we need to configure -our web application to connect to it. Open a new terminal window, navigate to -this repository's root directory, and run: - -```bash -# If you haven't already, enable yarn workspaces -yarn config set workspaces-experimental true - -# Restore dependencies -yarn install - -# Run our dev command, but with the local orthanc config -yarn run dev:orthanc -``` - -#### Configuration: Learn More - -> For more configuration fun, check out the -> [Essentials Configuration](../index.md) guide. - -Let's take a look at what's going on under the hood here. `yarn run dev:orthanc` -is running the `dev:orthanc` script in our project's `package.json` (inside -`platform/viewer`). That script is: - -```js -cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w -``` - -- `cross-env` sets three environment variables - - PROXY_TARGET: `/dicom-web` - - PROXY_DOMAIN: `http://localhost:8042` - - APP_CONFIG: `config/docker_nginx-orthanc.js` -- `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration - file. It will watch for changes and update as we develop. - -`PROXY_TARGET` and `PROXY_DOMAIN` tell our development server to proxy requests -to `Orthanc`. This allows us to bypass CORS issues that normally occur when -requesting resources that live at a different domain. - -The `APP_CONFIG` value tells our app which file to load on to `window.config`. -By default, our app uses the file at -`/platform/viewer/public/config/default.js`. Here is what that -configuration looks like: - -```js -window.config = { - routerBasename: '/', - extensions: [], - modes: [], - showStudyList: true, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', -}; -``` - -To learn more about how you can configure the OHIF Viewer, check out our -[Configuration Guide](../index.md). - -### Running DCM4CHEE - -dcm4che is a collection of open source applications for healthcare enterprise -written in Java programming language which implements DICOM standard. dcm4chee -(extra 'e' at the end) is dcm4che project for an Image Manager/Image Archive -which provides storage, retrieval and other functionalities. You can read more -about dcm4chee in their website [here](https://www.dcm4che.org/) - -DCM4chee installation is out of scope for these tutorials and can be found -[here](https://github.com/dcm4che/dcm4chee-arc-light/wiki/Run-minimum-set-of-archive-services-on-a-single-host) - -An overview of steps for running OHIF Viewer using a local DCM4CHEE is shown -below: - -
- -
- -[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light -[dcm4chee-docker]: - https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker -[orthanc]: https://www.orthanc-server.com/ -[orthanc-docker]: http://book.orthanc-server.com/users/docker.html -[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud -[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code -[osirix]: http://www.osirix-viewer.com/ -[horos]: https://www.horosproject.org/ -[default-config]: - https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js -[html-templates]: - https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates -[config-files]: - https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md deleted file mode 100644 index 62c53d69b93..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Static Files ---- - -# Static Files - -There is a binary DICOM to static file generator, which provides easily served -binary files. The files are all compressed in order to reduce space -significantly, and are pre-computed for the files required for OHIF, so that the -performance of serving the files is just the read from disk/write to http stream -time, without any extra processing time. - -The project for the static wado files is located here: [static-wado]: -https://github.com/wayfarer3130/static-wado - -It can be compiled with Java and Gradle, and then run against a set of dicom, in -the example located in /dicom/study1 outputting to /dicomweb, and then a server -run against that data, like this: - -``` -git clone https://github.com/wayfarer3130/static-wado.git -cd static-wado -./gradlew installDist -StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 -cd /dicomweb -npx http-server -p 5000 --cors -g -``` - -There is then a dev environment in the platform/viewer directory which can be -run against those files, like this: - -``` -cd platform/viewer -yarn dev:static -``` - -Additional studies can be added to the dicomweb by re-running the StaticWado -command. It will create a single studies.gz index file (JSON DICOM file, -compressed) containing an index of all studies created. There is then a small -extension to OHIF which performs client side indexing. - -The StaticWado command also knows how to deploy a client and dicomweb directory -to Amazon s3, which can then server files up directly. There is another build -setup build:aws in the viewer package.json to create such a deployment. diff --git a/platform/docs/versioned_docs/version-3.0/configuration/index.md b/platform/docs/versioned_docs/version-3.0/configuration/index.md deleted file mode 100644 index f51abd3d3ac..00000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/index.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - -After following the steps outlined in -[Getting Started](./../development/getting-started.md), you'll notice that the -OHIF Viewer has data for several studies and their images. You didn't add this -data, so where is it coming from? - -By default, the viewer is configured to connect to a remote server hosted by the -nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, -the time may come when you want to develop using your own data either locally or -remotely. - -## Configuration Files - -The configuration for our viewer is in the `platform/viewer/public/config` -directory. Our build process knows which configuration file to use based on the -`APP_CONFIG` environment variable. By default, its value is -[`config/default.js`][default-config]. The majority of the viewer's features, -and registered extension's features, are configured using this file. - -The simplest way is to update the existing default config: - -```js title="platform/viewer/public/config/default.js" -window.config = { - routerBasename: '/', - extensions: [], - modes: [], - showStudyList: true, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', -}; -``` - -> As you can see a new change in `OHIF-v3` is the addition of `dataSources`. You -> can build your own datasource and map it to the internal data structure of -> OHIF’s > metadata and enjoy using other peoples developed mode on your own -> data! -> -> You can read more about data sources at -> [Data Source section in Modes](../platform/modes/index.md) - -The configuration can also be written as a JS Function in case you need to -inject dependencies like external services: - -```js -window.config = ({ servicesManager } = {}) => { - const { UIDialogService } = servicesManager.services; - return { - cornerstoneExtensionConfig: { - tools: { - ArrowAnnotate: { - configuration: { - getTextCallback: (callback, eventDetails) => UIDialogService.create({... - } - } - }, - }, - routerBasename: '/', - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', - }; -}; -``` - - - -## Environment Variables - -We use environment variables at build and dev time to change the Viewer's -behavior. We can update the `HTML_TEMPLATE` to easily change which extensions -are registered, and specify a different `APP_CONFIG` to connect to an -alternative data source (or even specify different default hotkeys). - -| Environment Variable | Description | Default | -| -------------------- | -------------------------------------------------------------------------------------------------- | ------------------- | -| `HTML_TEMPLATE` | Which [HTML template][html-templates] to use as our web app's entry point. Specific to PWA builds. | `index.html` | -| `PUBLIC_URL` | The route relative to the host that the app will be served from. Specific to PWA builds. | `/` | -| `APP_CONFIG` | Which [configuration file][config-file] to copy to output as `app-config.js` | `config/default.js` | -| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` | -| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` | - -You can also create a new config file and specify its path relative to the build -output's root by setting the `APP_CONFIG` environment variable. You can set the -value of this environment variable a few different ways: - -- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~ - - Previous `react-scripts` functionality that we need to duplicate with - `dotenv-webpack` -- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~ - - Previous `react-scripts` functionality that we need to duplicate with - `dotenv-webpack` -- Using the `cross-env` package in a npm script: - - `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"` - -After updating the configuration, `yarn run build` to generate updated build -output. - - - - -[dcmjs-org]: https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[storescu]: https://support.dcmtk.org/docs/storescu.html -[webpack-proxy]: https://webpack.js.org/configuration/dev-server/#devserverproxy -[orthanc-docker-compose]: https://github.com/OHIF/Viewers/tree/master/.docker/Nginx-Orthanc - -[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light -[dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker -[orthanc]: https://www.orthanc-server.com/ -[orthanc-docker]: https://book.orthanc-server.com/users/docker.html -[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud -[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code -[osirix]: https://www.osirix-viewer.com/ -[horos]: https://www.horosproject.org/ -[default-config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js -[html-templates]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates -[config-files]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/_category_.json b/platform/docs/versioned_docs/version-3.0/deployment/_category_.json deleted file mode 100644 index 534be1dfb6d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Deployment", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md b/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md deleted file mode 100644 index b23463237a4..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Build for Production - -### Build Machine Requirements - -- [Node.js & NPM](https://nodejs.org/en/download/) -- [Yarn](https://yarnpkg.com/lang/en/docs/install/) -- [Git](https://www.atlassian.com/git/tutorials/install-git) - -### Getting the Code - -_With Git:_ - -```bash -# Clone the remote repository to your local machine -git clone https://github.com/OHIF/Viewers.git -``` - -More on: _[`git clone`](https://git-scm.com/docs/git-clone), -[`git checkout`](https://git-scm.com/docs/git-checkout)_ - -_From .zip:_ - -[OHIF/Viewers: master.zip](https://github.com/OHIF/Viewers/archive/master.zip) - -### Restore Dependencies & Build - -Open your terminal, and navigate to the directory containing the source files. -Next run these commands: - -```bash -# If you haven't already, enable yarn workspaces -yarn config set workspaces-experimental true - -# Restore dependencies -yarn install - -# Build source code for production -yarn run build -``` - -If everything worked as expected, you should have a new `dist/` directory in the -`platform/viewer/dist` folder. It should roughly resemble the following: - -```bash title="platform/viewer/dist/" -├── app-config.js -├── app.bundle.js -├── app.css -├── index.html -├── manifest.json -├── service-worker.js -└── ... -``` - -By default, the build output will connect to OHIF's publicly accessible PACS. If -this is your first time setting up the OHIF Viewer, it is recommended that you -test with these default settings. After testing, you can find instructions on -how to configure the project for your own imaging archive below. - -### Configuration - -The configuration for our viewer is in the `platform/viewer/public/config` -directory. Our build process knows which configuration file to use based on the -`APP_CONFIG` environment variable. By default, its value is -[`config/default.js`][default-config]. The majority of the viewer's features, -and registered extension's features, are configured using this file. - -The easiest way to apply your own configuration is to modify the `default.js` -file. For more advanced configuration options, check out our -[configuration essentials guide](../configuration/index.md). - -## Next Steps - -### Deploying Build Output - -_Drag-n-drop_ - -- [Netlify: Drop](./static-assets#netlify-drop) - -_Easy_ - -- [Surge.sh](./static-assets#surgesh) -- [GitHub Pages](./static-assets#github-pages) - -_Advanced_ - -- [AWS S3 + Cloudfront](./static-assets#aws-s3--cloudfront) -- [GCP + Cloudflare](./static-assets#gcp--cloudflare) -- [Azure](./static-assets#azure) - -### Testing Build Output Locally - -A quick way to test your build output locally is to spin up a small webserver. -You can do this by running the following commands in the `dist/` output -directory: - -```bash -# Install http-server as a globally available package -yarn global add http-server - -# Change the directory to the platform/viewer - -# Serve the files in our current directory -# Accessible at: `http://localhost:8080` -npx http-server ./dist -``` - -
- -
- -### Automating Builds and Deployments - -If you found setting up your environment and running all of these steps to be a -bit tedious, then you are in good company. Thankfully, there are a large number -of tools available to assist with automating tasks like building and deploying -web application. For a starting point, check out this repository's own use of: - -- [CircleCI][circleci]: [config.yaml][circleci-config] -- [Netlify][netlify]: [netlify.toml][netlify.toml] | - [build-deploy-preview.sh][build-deploy-preview.sh] - - -[circleci]: https://circleci.com/gh/OHIF/Viewers -[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml -[netlify]: https://app.netlify.com/sites/ohif/deploys -[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml -[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md deleted file mode 100644 index 532bf4cc636..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Google Cloud Healthcare - -> Coming soon - We are working on bringing Google Cloud Healthcare to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/deployment/index.md b/platform/docs/versioned_docs/version-3.0/deployment/index.md deleted file mode 100644 index 384aefc777d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/index.md +++ /dev/null @@ -1,335 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Deployment - -The OHIF Viewer can be embedded in other web applications via it's [packaged -script source][viewer-npm], or served up as a stand-alone PWA ([progressive web -application][pwa-url]) by building and hosting a collection of static assets. In -either case, you will need to configure your instance of the Viewer so that it -can connect to your data source (the database or PACS that provides the data -your Viewer will display). - -## Overview - -Our goal is to make deployment as simple and painless as possible; however, -there is an inherent amount of complexity in configuring and deploying web -applications. If you find yourself a little lost, please don't hesitate to -[reach out for help](/help) - -## Deployment Scenarios - -### Embedded Viewer (deprecated) - -`OHIF-v3` has deprecated deploying the viewer as an embedded viewer the number -of underlying libraries that run web workers are increasing for OHIF. An example -of these libraries is OHIF's 3D rendering functionality that is provided by -`vtk-js`. - -### Stand-alone Viewer - -Deploying the OHIF Viewer as a stand-alone web application provides many -benefits, but comes at the cost of time and complexity. Some benefits include: - -_Today:_ - -- Leverage [extensions](../platform/extensions/index.md) and - [modes](../platform/modes/index.md) to drop-in powerful new features -- Add routes and customize the viewer's workflow -- Finer control over styling and whitelabeling - -_In the future:_ - -- The ability to package the viewer for [App Store distribution][app-store] -- Leverage `service-workers` for offline support and speed benefits from caching - -#### Hosted Static Assets - -At the end of the day, a production OHIF Viewer instance is a collection of -HTML, CSS, JS, Font Files, and Images. We "build" those files from our -`source code` with configuration specific to our project. We then make those -files publicly accessible by hosting them on a Web Server. - -If you have not deployed a web application before, this may be a good time to -[reach out for help](/help), as these steps assume prior web development and -deployment experience. - -##### Part 1 - Build Production Assets - -"Building", or creating, the files you will need is the same regardless of the -web host you choose. You can find detailed instructions on how to configure and -build the OHIF Viewer in our -["Build for Production" guide](./build-for-production.md). - -##### Part 2 - Host Your App - -There are a lot of [benefits to hosting static assets][host-static-assets] over -dynamic content. You can find instructions on how to host your build's output -via one of these guides: - -_Drag-n-drop_ - -- [Netlify: Drop](./static-assets.md#netlify-drop) - -_Easy_ - -- [Surge.sh](./static-assets.md#surgesh) -- [GitHub Pages](./static-assets.md#github-pages) - -_Advanced_ - -- [AWS S3 + Cloudfront](./static-assets.md#aws-s3--cloudfront) -- [GCP + Cloudflare](./static-assets.md#gcp--cloudflare) -- [Azure](./static-assets.md#azure) - -## Data - -The OHIF Viewer is able to connect to any data source that implements the [DICOM -Web Standard][dicom-web-standard]. [DICOM Web][dicom-web] refers to RESTful -DICOM Services -- a recently standardized set of guidelines for exchanging -medical images and imaging metadata over the internet. Not all archives fully -support it yet, but it is gaining wider adoption. - -### Configure Connection - -If you have an existing archive and intend to host the OHIF Viewer at the same -domain name as your archive, then connecting the two is as simple as following -the steps layed out in our -[Configuration Essentials Guide](./../configuration/index.md). - -#### What if I don't have an imaging archive? - -We provide some guidance on configuring a local image archive in our -[Data Source Essentials](./../configuration/index.md#set-up-a-local-DICOM-server) -guide. Hosting an archive remotely is a little trickier. You can check out some -of our [advanced recipes](#recipes) for modeled setups that may work for you. - -#### What if I intend to host the OHIF Viewer at a different domain? - -There are two important steps to making sure this setup works: - -1. Your Image Archive needs to be exposed, in some way, to the open web. This - can be directly, or through a `reverse proxy`, but the Viewer needs _some - way_ to request its data. -2. \* Your Image Archive needs to have appropriate CORS (Cross-Origin Resource - Sharing) Headers - -> \* Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional -> HTTP headers to tell a browser to let a web application running at one origin -> (domain) have permission to access selected resources from a server at a -> different origin. - [MDN Web Docs: Web - Http - CORS][cors] - -Most image archives do not provide either of these features "out of the box". -It's common to use IIS, Nginx, or Apache to route incoming requests and append -appropriate headers. You can find an example of this setup in our -[Nginx + Image Archive Deployment Recipe](./nginx--image-archive.md). - -#### What if my archive doesn't support DicomWeb? - -It's possible to supply all Study data via JSON format, in the event you do not -have a DicomWeb endpoint. You can host all of the relevant files on any web -accessible server (Amazon S3, Azure Blob Storage, Local file server etc.) - -This JSON is supplied via the '?url=' query parameter. It should reference an -endpoint that returns **application/json** formatted text. - -If you do not have an API, you can simply return a text file containing the JSON -from any web server. - -You tell the OHIF viewer to use JSON by using the `dicomjson` datasource and -appending `'?url='` query to your mode's route: - -e.g. -`https://my-test-ohif-server/myMode/dicomjson?url=https://my-json-server/study-uid.json` - -The returned JSON object must contain a single root object with a 'studies' -array. - -You can read more about using different data sources for mode's routes -[here](../platform/modes/routes.md#route-path) - -_Sample JSON format:_ - -```json -{ - "studies": [ - { - "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78", - "StudyDescription": "BRAIN SELLA", - "StudyDate": "20010108", - "StudyTime": "120022", - "PatientName": "MISTER^MR", - "PatientId": "832040", - "series": [ - { - "SeriesDescription": "SAG T-1", - "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", - "SeriesNumber": 2, - "SeriesDate": "20010108", - "SeriesTime": "120318", - "Modality": "MR", - "instances": [ - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 3, - "AcquisitionNumber": 0, - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.390625, 0.390625], - "HighBit": 15, - "ImageOrientationPatient": [0, 1, 0, 0, 0, -1], - "ImagePositionPatient": [11.6, -92.5, 98.099998], - "FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470", - "ImageType": ["ORIGINAL", "PRIMARY", "OTHER"], - "Modality": "MR", - "SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124", - "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", - "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78" - }, - "url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm" - } - ] - } - ] - } - ] -} -``` - -More info on this JSON format can be found here -[Issue #1500](https://github.com/OHIF/Viewers/issues/1500) - -**Implementation Notes:** - - - -1. For each instance url (dicom object) in the returned JSON, you must prefix - the `url` with `dicomjson:` in order for the cornerstone image loader to - retrieve it correctly. eg. `https://image-server/my-image.dcm` ---> - `dicomjson:https://image-server/my-image.dcm` -2. The JSON format above is compatible with >= v3.7.8 of the application in `V2` - version. Older versions of the viewer used a different JSON format. As of - 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does - not support this format yet. -3. The JSON format is case-sensitive. Please ensure you have matched casing with - the naturalised Dicom format referenced in - [Issue #1500](https://github.com/OHIF/Viewers/issues/1500). - -_CORS Issues (Cross-Origin Resource Sharing)_ - -If you host a JSON API or Images on a different domain from the app itself, -you will likely have CORS issues. This will also happen when testing from -Localhost and reaching out to remote servers. Even if the domain is the same, -different ports, subdomains or protocols (https vs http) will also cause CORS -errors. You will to need add a configuration on each server hosting these assets -to allow your App server origin. - -For example: - -Let's assume your application is hosted on `https://my-ohif-server.com`. - -Your JSON API is hosted on `https://my-json-api.aws.com` - -And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com` - -When you first start your application, browsing to -`https://my-ohif-server.com/myMode/dicomjson?url=https://my-json-api.aws.com/api/my-json-study-info.json`, -you will likely get a CORS error in the browser console as it tries to connect -to `https://my-json-api.aws.com`. - -Adding a setting on the JSON server to allow the CORS origin = -`https://my-ohif-server.com` should solve this. - -Next, you will likely get a similar CORS error, as the browser tries to go to -`https://my-s3-bucket.aws.com`. You will need to go to the S3 bucket -configuration, and add a CORS setting to allow origin = -`https://my-ohif-server.com`. - -Essentially, whenever the application connects to a remote resource, you will -need to add the applications url to the allowed CORS Origins on that resource. -Adding an origin similar to https://localhost:3000 will also allow for local -testing. - -### Securing Your Data - -Coming soon - - - -### Recipes - -We've included a few recipes for common deployment scenarios. There are many, -many possible configurations, so please don't feel limited to these setups. -Please feel free to suggest or contribute your own recipes. - -- [Build for Production](./build-for-production.md) -- [Static](./static-assets.md) -- [Nginx + Image Archive](./nginx--image-archive.md) -- [User Account Control](./user-account-control.md) - - - - -[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer -[pwa-url]: https://developers.google.com/web/progressive-web-apps/ -[static-assets-url]: https://www.maxcdn.com/one/visual-glossary/static-content/ -[app-store]: https://medium.freecodecamp.org/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned-7cb3f56daf9b -[dicom-web-standard]: https://www.dicomstandard.org/dicomweb/ -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[host-static-assets]: https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/ -[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS -[code-flows]: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660 -[code-sandbox]: https://codesandbox.io/s/viewer-script-tag-tprch - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md b/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md deleted file mode 100644 index 79f729d2fb5..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Nginx + Image Archive - -> DISCLAIMER! We make no claims or guarantees of this approach's security. If in -> doubt, enlist the help of an expert and conduct proper audits. - -At a certain point, you may want others to have access to your instance of the -OHIF Viewer and its medical imaging data. This post covers one of many potential -setups that accomplish that. Please note, noticeably absent is user account -control. - -Do not use this recipe to host sensitive medical data on the open web. Depending -on your company's policies, this may be an appropriate setup on an internal -network when protected with a server's basic authentication. For a more robust -setup, check out our [user account control recipe](./user-account-control) -that builds on the lessons learned here. - -## Overview - -Our two biggest hurdles when hosting our image archive and web client are: - -- Risks related to exposing our PACS to the network -- Cross-Origin Resource Sharing (CORS) requests - -### Handling Web Requests - -We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web -requests. Nginx is open source software for web serving, reverse proxying, -caching, and more. It's designed for maximum performance and stability -- -allowing us to more reliably serve content than Orthanc's built-in server can. - -More specifically, we accomplish this by using a -[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve -resources from our image archive (Orthanc), and when accessing its web admin. - -> A reverse proxy is a type of proxy server that retrieves resources on behalf -> of a client from one or more servers. These resources are then returned to the -> client, appearing as if they originated from the proxy server itself. - -### CORS Issues - -Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to -tell a browser which web applications have permission to access selected -resources from a server at a different origin (domain, protocol, port). IE. By -default, a Web App located at `http://my-website.com` can't access resources -hosted at `http://not-my-website.com` - -We can solve this one of two ways: - -1. Have our Image Archive located at the same domain as our Web App -2. Add appropriate `Access-Control-Allow-*` HTTP headers - -**This solution uses the first approach.** - -You can read more about CORS in this Medium article: [Understanding -CORS][understanding-cors] - -### Diagram - -This setup allows us to create a setup similar to the one pictured below: - - -![nginX](../assets/img/nginx-image-archive.png) - - -- All web requests are routed through `nginx` on our `OpenResty` image -- `/pacs` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints -- `/pacs-admin` is a reverse proxy for `orthanc`'s Web Admin -- All static resources for OHIF Viewer are served up by `nginx` when a matching - route for that resource is requested - -## Getting Started - -### Requirements - -- Docker - - [Docker for Mac](https://docs.docker.com/docker-for-mac/) - - [Docker for Windows](https://docs.docker.com/docker-for-windows/) - -_Not sure if you have `docker` installed already? Try running `docker --version` -in command prompt or terminal_ - -### Setup - -- Navigate to `viewer` folder inside `platform` -- then: `cd .recipes/OpenResty-Orthanc` -- run: `docker-compose up --build` -- Navigate to `127.0.0.1` for the viewer -- Navigate to `127.0.0.1/pacs-admin` for uploading studies - - -You can see the overview of the mentioned steps: - - - -
- -
- - - -### Troubleshooting - -_Exit code 137_ - -This means Docker ran out of memory. Open Docker Desktop, go to the `advanced` -tab, and increase the amount of Memory available. - -_Cannot create container for service X_ - -Use this one with caution: `docker system prune` - -_X is already running_ - -Stop running all containers: - -- Win: `docker ps -a -q | ForEach { docker stop $_ }` -- Linux: `docker stop $(docker ps -a -q)` - - -_Traceback (most recent call last):_ - _File "urllib3/connectionpool.py", line 670, in urlopen_ - _...._ - -Are you sure your docker is running? see explanation [here](https://github.com/docker/compose/issues/7896) - - -### Configuration - -After verifying that everything runs with default configuration values, you will -likely want to update: - -- The domain: `http://127.0.0.1` - -#### OHIF Viewer - -The OHIF Viewer's configuration is imported from a static `.js` file. The -configuration we use is set to a specific file when we build the viewer, and -determined by the env variable: `APP_CONFIG`. You can see where we set its value -in the `dockerfile` for this solution: - -`ENV APP_CONFIG=config/docker_openresty-orthanc.js` - -You can find the configuration we're using here: -`/public/config/docker_openresty-orthanc.js` - -To rebuild the `webapp` image created by our `dockerfile` after updating the -Viewer's configuration, you can run: - -- `docker-compose build` OR -- `docker-compose up --build` - -#### Other - -All other files are found in: `/docker/OpenResty-Orthanc/` - -| Service | Configuration | Docs | -| ----------------- | --------------------------------- | ------------------------------------------- | -| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! | -| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] | -| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] | - -## Next Steps - -### Deploying to Production - -While these configuration and docker-compose files model an environment suitable -for production, they are not easy to deploy "as is". You can either: - -- Manually recreate this environment and deploy built application files **OR** -- Deploy to a cloud kubernetes provider like - [Digital Ocean](https://www.digitalocean.com/products/kubernetes/) **OR** - - [See a full list of cloud providers here](https://landscape.cncf.io/category=cloud&format=card-mode&grouping=category) -- Find and follow your preferred provider's guide on setting up - [swarms and stacks](https://docs.docker.com/get-started/) - -### Adding SSL - -Adding SSL registration and renewal for your domain with Let's Encrypt that -terminates at Nginx is an incredibly important step toward securing your data. -Here are some resources, specific to this setup, that may be helpful: - -- [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl) -- [Let's Encrypt + Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) - -While we terminate SSL at Nginx, it may be worth using self-signed certificates -for communication between services. - -- [SSL Termination for TCP Upstream Servers](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) - -### Use PostgresSQL w/ Orthanc - -Orthanc can handle a large amount of data and requests, but if you find that -requests start to slow as you add more and more studies, you may want to -configure your Orthanc instance to use PostgresSQL. Instructions on how to do -that can be found in the -[`Orthanc Server Book`](http://book.orthanc-server.com/users/docker.html), under -"PostgreSQL and Orthanc inside Docker" - -### Improving This Guide - -Here are some improvements this guide would benefit from, and that we would be -more than happy to accept Pull Requests for: - -- SSL Support -- Complete configuration with `.env` file (or something similar) -- Any security issues -- One-click deploy to a cloud provider - -## Resources - -### Misc. Helpful Commands - -_Check if `nginx.conf` is valid:_ - -```bash -docker run --rm -t -a stdout --name my-openresty -v $PWD/config/:/usr/local/openresty/nginx/conf/:ro openresty/openresty:alpine-fat openresty -c /usr/local/openresty/nginx/conf/nginx.conf -t -``` - -_Interact w/ running container:_ - -`docker exec -it CONTAINER_NAME bash` - -_List running containers:_ - -`docker ps` - -### Referenced Articles - -For more documentation on the software we've chosen to use, you may find the -following resources helpful: - -- [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html) -- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/) -- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/) - -For a different take on this setup, check out the repositories our community -members put together: - -- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker) -- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker) - - - - - -[nginx]: https://www.nginx.com/resources/glossary/nginx/ -[understanding-cors]: https://medium.com/@baphemot/understanding-cors-18ad6b478e2b -[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration -[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc - -[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/dockerfile -[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/nginx.conf -[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md b/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md deleted file mode 100644 index 767ad692802..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Deploy Static Assets - -> WARNING! All of these solutions stand-up a publicly accessible web viewer. Do -> not hook your hosted viewer up to a sensitive source of data without -> implementing authentication. - -There are a lot of options for deploying static assets. Some services, like -`netlify` and `surge.sh`, specialize in static websites. You'll notice that -deploying with them requires much less time and effort, but comes at the cost of -less product offerings. - -While not required, it can simplify things to host your Web Viewer alongside -your image archive. Services with more robust product offerings, like -`Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able -to accommodate this setup. - -_Drag-n-drop_ - -- [Netlify: Drop](#netlify-drop) - -_Easy_ - -- [Surge.sh](#surgesh) -- [GitHub Pages](#github-pages) - -_Advanced_ - -- [Deploy Static Assets](#deploy-static-assets) - - [Drag-n-drop](#drag-n-drop) - - [Netlify Drop](#netlify-drop) - - [Easy](#easy) - - [Surge.sh](#surgesh) - - [GitHub Pages](#github-pages) - - [Advanced](#advanced) - - [AWS S3 + Cloudfront](#aws-s3--cloudfront) - - [GCP + Cloudflare](#gcp--cloudflare) - - [Azure](#azure) - -## Drag-n-drop - -### Netlify Drop - - -
- -
- - -_GIF demonstrating deployment with Netlify Drop_ - -1. https://app.netlify.com/drop -2. Drag your `build/` folder on to the drop target -3. ... -4. _annnd you're done_ - -**Features:** - -- Custom domains & HTTPS -- Instant Git integration -- Continuous deployment -- Deploy previews -- Access to add-ons - -(Non-free tiers include identity, FaaS, Forms, etc.) - -Learn more about [Netlify on their website](https://www.netlify.com/) - -## Easy - -### Surge.sh - -> Static web publishing for Front-End Developers. Simple, single-command web -> publishing. Publish HTML, CSS, and JS for free, without leaving the command -> line. - -![surge.sh deploy example](../assets/img/surge-deploy.gif) - -_GIF demonstrating deployment with surge_ - -```shell -# Add surge command -yarn global add surge - -# In the build directory -surge -``` - -**Features:** - -- Free custom domain support -- Free SSL for surge.sh subdomains -- pushState support for single page apps -- Custom 404.html pages -- Barrier-free deployment through the CLI -- Easy integration into your Grunt toolchain -- Cross-origin resource support -- And more… - -Learn more about [surge.sh on their website](https://surge.sh/) - -### GitHub Pages - -> WARNING! While great for project sites and light use, it is not advised to use -> GitHub Pages for production workloads. Please consider using a different -> service for mission critical applications. - -> Websites for you and your projects. Hosted directly from your GitHub -> repository. Just edit, push, and your changes are live. - -This deployment strategy makes more sense if you intend to maintain your project in -a GitHub repository. It allows you to specify a `branch` or `folder` as the -target for a GitHub Page's website. As you push code changes, the hosted content -updates to reflect those changes. - -1. Head over to GitHub.com and create a new repository, or go to an existing - one. Click on the Settings tab. -2. Scroll down to the GitHub Pages section. Choose the `branch` or `folder` you - would like as the "root" of your website. -3. Fire up a browser and go to `http://username.github.io/repository` - -Configuring Your Site: - -- [Setting up a custom domain](https://help.github.com/en/articles/using-a-custom-domain-with-github-pages) -- [Setting up SSL](https://help.github.com/en/articles/securing-your-github-pages-site-with-https) - -Learn more about [GitHub Pages on its website](https://pages.github.com/) - -## Advanced - -All of these options, while using providers with more service offerings, -demonstrate how to host the viewer with their respective file storage and CDN -offerings. While you can serve your static assets this way, if you're going -through the trouble of using AWS/GCP/Azure, it's more likely you're doing so to -avoid using a proxy or to simplify authentication. - -If that is the case, check out some of our more advanced `docker` deployments -that target these providers from the left-hand sidepanel. - -These guides can be a bit longer and an update more frequently. To provide -accurate documentation, we will link to each provider's own recommended steps: - -### AWS S3 + Cloudfront - -- [Host a Static Website](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) -- [Speed Up Your Website with Cloudfront](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html) - -### GCP + Cloudflare - -- [Things to Know Before Getting Started](https://code.luasoftware.com/tutorials/google-cloud-storage/things-to-know-before-hosting-static-website-on-google-cloud-storage/) -- [Hosting a Static Website on GCP](https://cloud.google.com/storage/docs/hosting-static-website) - -### Azure - -- [Host a Static Website](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website) -- [Add SSL Support](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-https-custom-domain-cdn) -- [Configure a Custom Domain](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-custom-domain-name) diff --git a/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md b/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md deleted file mode 100644 index e411cc7d535..00000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 5 ---- - -# User Account Control - -> Coming soon - We are working on bringing the User Account Control to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/development/_category_.json b/platform/docs/versioned_docs/version-3.0/development/_category_.json deleted file mode 100644 index 8627cac4920..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Development", - "position": 5 -} diff --git a/platform/docs/versioned_docs/version-3.0/development/architecture.md b/platform/docs/versioned_docs/version-3.0/development/architecture.md deleted file mode 100644 index 4bcebb0175e..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/architecture.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Architecture ---- - -# Architecture - -In order to achieve a platform that can support various workflows and be -extensible for the foreseeable future we went through extensive planning of -possible use cases and decided to significantly change and improve the -architecture. - -Below, we aim to demystify that complexity by providing insight into how -`OHIF Platform` is architected, and the role each of its dependent libraries -plays. - -## Overview - -The [OHIF Medical Image Viewing Platform][viewers-project] 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 # default functionalities -│ ├── cornerstone # 2D images w/ Cornerstone.js -│ ├── measurement-tracking # measurement tracking -│ ├── dicom-sr # Structured reports -│ └── dicom-pdf # View DICOM wrapped PDFs in viewport -│ -├── modes -│ └── longitudinal # longitudinal measurement tracking mode -│ -├── platform -│ ├── core # Business Logic -│ ├── i18n # Internationalization Support -│ ├── ui # React component library -│ └── viewer # Connects platform and extension projects -│ -├── ... # misc. shared configuration -├── lerna.json # MonoRepo (Lerna) settings -├── package.json # Shared devDependencies and commands -└── README.md -``` - -OHIF v3 is composed of the following components, described in detail in further -sections: - -- `@ohif/viewer`: The core framework that controls extension registration, mode - composition and routing. -- `@ohif/core`: A library of useful and reusable medical imaging functionality - for the web. -- `@ohif/ui`: A library of reusable components to build OHIF-styled applications - with. -- `Extensions`: A set of building blocks for building applications. The OHIF org - maintains a few core libraries. -- `Modes`: Configuration objects that tell @ohif/viewer how to compose - extensions to build applications on different routes of the platform. - -## Extensions - -The `extensions` directory contains many packages that provide essential -functionalities such as rendering, study/series browsers, measurement tracking -that modes can consume to enable a certain workflow. Extensions have had their -behavior changed in `OHIF-v3` and their api is expanded. In summary: - -> In `OHIF-v3`, extensions no longer automatically hook themselves to the app. -> Now, registering an extension makes its component available to `modes` that -> wish to use them. Basically, extensions in `OHIF-v3` are **building blocks** -> for building applications. - -OHIF team maintains several high value and commonly used functionalities in its -own extensions. For a list of extensions maintained by OHIF, -[check out this helpful table](../platform/extensions/index.md#maintained-extensions). -As an example `default` extension provides a default viewer layout, a -study/series browser and a datasource that maps to a DICOMWeb compliant backend. - -[Click here to read more about extensions!](../platform/extensions/index.md) - -## Modes - -The `modes` directory contains workflows that can be registered with OHIF within -certain `routes`. The mode will get used once the user opens the viewer on the -registered route. - -OHIF extensions were designed to provide certain core functionalities for -building your viewer. However, often in medical imaging we face a specific use -case in which we are using some core functionalities, adding our specific UI, -and use it in our workflows. Previously, to achieve this you had to create an -extension to add have such feature. `OHIF-v3` introduces `Modes` to enable -building such workflows by re-using the core functionalities from the -extensions. - -Some common workflows may include: - -- Measurement tracking for lesions -- Segmentation of brain abnormalities -- AI probe mode for detecting prostate cancer - -In the mentioned modes above, they will share the same core rendering module -that the `default` extension provides. However, segmentation mode will require -segmentation tools which is not needed for the other two. As you can see, modes -are a layer on top of extensions, that you can configure in order to achieve -certain workflows. - -To summarize the difference between extensions and modes in `OHIF-v3` and -extensions in `OHIF-v2` - -> - `Modes` are configuration objects that tell _@ohif/viewer_ how to compose -> extensions to build applications on different routes of the platform. -> - In v2 extensions are “plugins” that add functionality to a core viewer. -> - In v3 extensions are building blocks that a mode uses to build an entire -> viewer layout. - -[Click here to read more about modes!](../platform/modes/index.md) - -## Platform - -### `@ohif/viewer` - -This library is the core library which consumes modes and extensions and builds -an application. Extensions can be passed in as app configuration and will be -consumed and initialized at the appropriate time by the application. Upon -initialization the viewer will consume extensions and modes and build up the -route desired, these can then be accessed via the study list, or directly via -url parameters. - -Upon release modes will also be plugged into the app via configuration, but this -is still an area which is under development/discussion, and they are currently -pulled from the window in beta. - -Future ideas for this framework involve only adding modes and fetching the -required extension versions at either runtime or build time, but this decision -is still up for discussion. - -### `@ohif/core` - -OHIF core is a carefully maintained and tested set of web-based medical imaging -functions and classes. This library includes managers and services used from -within the viewer app. - -OHIF core is largely similar to the @ohif/core library in v2, however a lot of -logic has been moved to extensions: however all logic about DICOMWeb and other -data fetching mechanisms have been pulled out, as these now live in extensions, -described later. - -### `@ohif/ui` - -Firstly, a large time-consumer/barrier for entry we discovered was building new -UI in a timely manner that fit OHIF’s theme. For this reason we have built a new -UI component library which contains all the components one needs to build their -own viewer. - -These components are presentational only, so you can reuse them with whatever -logic you desire. As the components are presentational, you may swap out -@ohif/ui for a custom UI library with conforming API if you wish to white label -the viewer. The UI library is here to make development easier and quicker, but -it is not mandatory for extension components to use. - -[Check out our component library!](https://react.ohif.org/) - -## Overview of the architecture - -OHIF-v3 architecture can be seen in the following figure. We will explore each -piece in more detail. - -![mode-archs](../assets/img/mode-archs.png) - -## Common Questions - -> Can I create my own Viewer using Vue.js or Angular.js? - -You can, but you will not be able to leverage as much of the existing code and -components. `@ohif/core` could still be used for business logic, and to provide -a model for extensions. `@ohif/ui` would then become a guide for the components -you would need to recreate. - -> When I want to implement a functionality, should it be in the mode or in an -> extension? - -This is a great question. Modes are designed to consume extensions, so you -should implement your functionality in one of the modules of your new extension, -and let the mode consume it. This way, in the future, if you needed another mode -that utilizes the same functionality, you can easily hook the extension to the -new mode as well. - - - - -[monorepo]: https://github.com/OHIF/Viewers/issues/768 -[viewers-project]: https://github.com/OHIF/Viewers -[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer -[pwa]: https://developers.google.com/web/progressive-web-apps/ -[configuration]: ../configuration/index.md -[extensions]: ../platform/extensions/index.md -[core-github]: https://github.com/OHIF/viewers/platform/core -[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui - diff --git a/platform/docs/versioned_docs/version-3.0/development/continous-integration.md b/platform/docs/versioned_docs/version-3.0/development/continous-integration.md deleted file mode 100644 index 3c3124af83b..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/continous-integration.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: Continous Integration ---- - -# Continous Integration (CI) - -This repository uses `CircleCI` and `Netlify` for continous integration. - -## Deploy Previews - -[Netlify Deploy previews][deploy-previews] are generated for every pull request. -They allow pull request authors and reviewers to "Preview" the OHIF Viewer as if -the changes had been merged. - -Deploy previews can be configured by modifying the `netlify.toml` file in the -root of the repository. Some additional scripts/assets for netlify are included -in the root `.netlify` directory. - -## Workflows - -[CircleCI Workflows][circleci-workflows] are a set of rules for defining a -collection of jobs and their run order. They are self-documenting and their -configuration can be found in our CircleCI configuration file: -`.circleci/config.yml`. - -### Workflow: PR_CHECKS - -The PR_CHECKS workflow (Pull Request Checks) runs our automated unit and -end-to-end tests for every code check-in. These tests must all pass before code -can be merged to our `master` branch. - -![PR_CHECKS](../assets/img/WORKFLOW_PR_CHECKS.png) - -### Workflow: PR_OPTIONAL_DOCKER_PUBLISH - -The PR_OPTIONAL_DOCKER_PUBLISH workflow allows for "manual approval" to publish -the pull request as a tagged docker image. This is helpful when changes need to -be tested with the Google Adapter before merging to `master`. - -![PR_Workflow](../assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png) - -> NOTE: This workflow will fail unless it's for a branch on our `upstream` -> repository. If you need this functionality, but the branch is from a fork, -> merge the changes to a short-lived `feature/` branch on `upstream` - -### Workflow: DEPLOY - -The DEPLOY workflow deploys the OHIF Viewer when changes are merged to master. -It uses the Netlify CLI to deploy assets created as part of the repository's PWA -Build process (`yarn run build`). The workflow allows for "Manual Approval" to -promote the build to `STAGING` and `PRODUCTION` environments. - -![WORKFLOW_DEPLOY](../assets/img/WORKFLOW_DEPLOY.png) - -| Environment | Description | URL | -| ----------- | ---------------------------------------------------------------------------------- | --------------------------------------------- | -| Development | Always reflects latest changes on `master` branch. | [Netlify][netlify-dev] / [OHIF][ohif-dev] | -| Staging | For manual testing before promotion to prod. Keeps development workflow unblocked. | [Netlify][netlify-stage] / [OHIF][ohif-stage] | -| Production | Stable, tested, updated less frequently. | [Netlify][netlify-prod] / [OHIF][ohif-prod] | - -### Workflow: RELEASE - -The RELEASE workflow publishes our `npm` packages, updated documentation, and -`docker` image when changes are merged to master. `Lerna` and "Semantic Commit -Syntax" are used to independently version and publish the many packages in our -monorepository. If a new version is cut/released, a Docker image is created. -Documentation is generated with `gitbook` and pushed to our `gh-pages` branch. -GitHub hosts the `gh-pages` branch with GitHub Pages. - -- Platform Packages: https://github.com/ohif/viewers/#platform -- Extension Packages: https://github.com/ohif/viewers/#extensions -- Documentation: https://docs.ohif.org/ - -![WORKFLOW_RELEASE](../assets/img/WORKFLOW_RELEASE.png) - - - - -[deploy-previews]: https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/ -[circleci-workflows]: https://circleci.com/docs/2.0/workflows/ -[netlify-dev]: https://ohif-dev.netlify.com -[netlify-stage]: https://ohif-stage.netlify.com -[netlify-prod]: https://ohif-prod.netlify.com -[ohif-dev]: https://viewer-dev.ohif.org -[ohif-stage]: https://viewer-stage.ohif.org -[ohif-prod]: https://viewer-prod.ohif.org - diff --git a/platform/docs/versioned_docs/version-3.0/development/contributing.md b/platform/docs/versioned_docs/version-3.0/development/contributing.md deleted file mode 100644 index 763be20dd08..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/contributing.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Contributing ---- - -# Contributing - -## How can I help? - -Fork the repository, make your change and submit a pull request. If you would -like to discuss the changes you intend to make to clarify where or how they -should be implemented, please don't hesitate to create a new issue. At a -minimum, you may want to read the following documentation: - -- [Getting Started](/development/getting-started.md) -- [Architecture](./architecture.md) - -Pull requests that are: - -- Small -- [Well tested](./testing.md) -- Decoupled - -Are much more likely to get reviewed and merged in a timely manner. - -## When changes impact multiple repositories - -While this can be tricky, we've tried to reduce how often this situation crops -up this with our [recent switch to a monorepo][monorepo]. Our maintained -extensions, ui components, internationalization library, and business logic can -all be developed by simply running `yarn run dev` from the repository root. - -Testing the viewer with locally developed, unpublished package changes from a -package outside of the monorepo is most common with extension development. Let's -demonstrate how to accomplish this with two commonly forked extension -dependencies: - -### `cornerstone-tools` - -On your local file system: - -```bash title="/my-projects/" -├── cornerstonejs/cornerstone-tools -└── ohif/viewers -``` - -- Open a terminal/shell -- Navigate to `cornerstonejs/cornerstone-tools` - - `yarn install` - - [`yarn link`](https://yarnpkg.com/en/docs/cli/link) - - `yarn run dev` - -* Open a new terminal/shell -* Navigate to `ohif/viewers` (the root of ohif project) - - `yarn install` - - [`yarn link cornerstone-tools`](https://yarnpkg.com/en/docs/cli/link) - - `yarn run dev` - -As you make changed to `cornerstone-tools`, and it's output is rebuilt, you -should see the following behavior: - -![tools](..//assets/img/cornerstone-tools-link.gif) - -If you wish to stop using your local package, run the following commands in the -`ohif/viewers` repository root: - -- `yarn unlink cornerstone-tools` -- `yarn install --force` - - - -#### Other linkage notes - -We're still working out some of the kinks with local package development as -there are a lot of factors that can influence the behavior of our development -server and bundler. If you encounter issues not addressed here, please don't -hesitate to reach out on GitHub. - -Sometimes you might encounter a situation where the linking doesn't work as -expected. This might happen when there are multiple linked packages with the -same name. You can [remove][unlink] the linked packages inside yarn and try -again. - -## Any guidance on submitting changes? - -While we do appreciate code contributions, triaging and integrating contributed -code changes can be very time consuming. Please consider the following tips when -working on your pull requests: - -- Functionality is appropriate for the repository. Consider creating a GitHub - issue to discuss your suggested changes. -- The scope of the pull request is not too large. Please consider separate pull - requests for each feature as big pull requests are very time consuming to - understand. - -We will provide feedback on your pull requests as soon as possible. Following -the tips above will help ensure your changes are reviewed. - - - - - - -[example-url]: https://deploy-preview-237--ohif.netlify.com/viewer/?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/sampleDICOM.json -[pr-237]: https://github.com/OHIF/Viewers/pull/237 -[monorepo]: https://github.com/OHIF/Viewers/issues/768 -[unlink]: https://stackoverflow.com/questions/58459698/is-there-a-command-to-unlink-all-yarn-packages-yarn-unlink-all - diff --git a/platform/docs/versioned_docs/version-3.0/development/getting-started.md b/platform/docs/versioned_docs/version-3.0/development/getting-started.md deleted file mode 100644 index b9b3bc5d51a..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/getting-started.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Getting Started ---- - -# Getting Started - -## Setup - -### Fork & Clone - -If you intend to contribute back changes, or if you would like to pull updates -we make to the OHIF Viewer, then follow these steps: - -- [Fork][fork-a-repo] the [OHIF/Viewers][ohif-viewers-repo] repository -- [Create a local clone][clone-a-repo] of your fork - - `git clone https://github.com/YOUR-USERNAME/Viewers` -- Add OHIF/Viewers as a [remote repository][add-remote-repo] labeled `upstream` - - Navigate to the cloned project's directory - - `git remote add upstream https://github.com/OHIF/Viewers.git` - -With this setup, you can now [sync your fork][sync-changes] to keep it -up-to-date with the upstream (original) repository. This is called a "Triangular -Workflow" and is common for Open Source projects. The GitHub blog has a [good -graphic that illustrates this setup][triangular-workflow]. - -### Private - -Alternatively, if you intend to use the OHIF Viewer as a starting point, and you -aren't as concerned with syncing updates, then follow these steps: - -1. Navigate to the [OHIF/Viewers][ohif-viewers] repository -2. Click `Clone or download`, and then `Download ZIP` -3. Use the contents of the `.zip` file as a starting point for your viewer - -> NOTE: It is still possible to sync changes using this approach. However, -> submitting pull requests for fixes and features are best done with the -> separate, forked repository setup described in "Fork & Clone" - -## Developing - -### Requirements - -- [Node.js & NPM](https://nodejs.org/en/) -- [Yarn](https://yarnpkg.com/en/) -- Yarn workspaces should be enabled: - - `yarn config set workspaces-experimental true` - -### Kick the tires - -Navigate to the root of the project's directory in your terminal and run the -following commands: - -```bash -# Switch to the v3 branch -git switch v3-stable - -# Restore dependencies -yarn install - -# Start local development server -yarn run dev -``` - -You should see the following output: - -```bash -@ohif/viewer: i 「wds」: Project is running at http://localhost:3000/ -@ohif/viewer: i 「wds」: webpack output is served from / -@ohif/viewer: i 「wds」: Content not from webpack is served from D:\code\ohif\Viewers\platform\viewer -@ohif/viewer: i 「wds」: 404s will fallback to /index.html - -# And a list of all generated files -``` - -### 🎉 Celebrate 🎉 - -
- -
- -### Building for Production - -> More comprehensive guides for building and publishing can be found in our -> [deployment docs](./../deployment/index.md) - -```bash -# Build static assets to host a PWA -yarn run build -``` - -## Troubleshooting - -- If you receive a _"No Studies Found"_ message and do not see your studies, try - changing the Study Date filters to a wider range. -- If you see a 'Loading' message which never resolves, check your browser's - JavaScript console inside the Developer Tools to identify any errors. - - - - -[fork-a-repo]: https://help.github.com/en/articles/fork-a-repo -[clone-a-repo]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork -[add-remote-repo]: https://help.github.com/en/articles/fork-a-repo#step-3-configure-git-to-sync-your-fork-with-the-original-spoon-knife-repository -[sync-changes]: https://help.github.com/en/articles/syncing-a-fork -[triangular-workflow]: https://github.blog/2015-07-29-git-2-5-including-multiple-worktrees-and-triangular-workflows/#improved-support-for-triangular-workflows -[ohif-viewers-repo]: https://github.com/OHIF/Viewers -[ohif-viewers]: https://github.com/OHIF/Viewers - diff --git a/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md b/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md deleted file mode 100644 index cf8af80f581..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md +++ /dev/null @@ -1,291 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: OHIF CLI ---- - -# OHIF Command Line Interface - -OHIF-v3 architecture has been re-designed to enable building applications that -are easily extensible to various use cases (Modes) that behind the scene would -utilize desired functionalities (Extensions) to reach the goal of the use case. -Now, the question is _how to create/remove/install/uninstall an extension and/or -mode?_ - -You can use the `cli` script that comes with the OHIF monorepo to achieve these -goals. - -:::note Info -In the long-term, we envision our `cli` tool to be a separate installable -package that you can invoke anywhere on your local system to achieve the same -goals. In the meantime, `cli` will remain as part of the OHIF monorepo and needs -to be invoked using the `yarn` command. -::: - - -## CLI Installation - -You don't need to install the `cli` currently. You can use `yarn` to invoke its -commands. - -## Commands - -:::note Important -All commands should run from the root of the monorepo. -::: - - -There are various commands that can be used to interact with the OHIF-v3 CLI. If -you run the following command, you will see a list of available commands. - -``` -yarn run cli --help -``` - -which will output - -``` -OHIF CLI - -Options: - -V, --version output the version number - -h, --help display help for command - -Commands: - create-extension Create a new template extension - create-mode Create a new template Mode - add-extension [version] Adds an ohif extension - remove-extension removes an ohif extension - add-mode [version] Removes an ohif mode - remove-mode Removes an ohif mode - link-extension Links a local OHIF extension to the Viewer to be used for development - unlink-extension Unlinks a local OHIF extension from the Viewer - link-mode Links a local OHIF mode to the Viewer to be used for development - unlink-mode Unlinks a local OHIF mode from the Viewer - list List Added Extensions and Modes - search [options] Search NPM for the list of Modes and Extensions - help [command] display help for command -``` - -As seen there are commands for you such as: `create-extension`, `create-mode`, -`add-extension`, `remove-extension`, `add-mode`, `remove-mode`, -`link-extension`, `unlink-extension`, `link-mode`, `unlink-mode`, `list`, -`search`, and `help`. Here we will go through each of the commands and describe -them. - -### create-mode - -If you need to create a new mode, you can use the `create-mode` command. This -command will create a new mode template in the directory that you specify. -The command will ask you couple of information/questions in order -to properly create the mode metadata in the `package.json` file. - -```bash -yarn run cli create-mode -``` - -
- -![image](../assets/img/create-mode.png) - - -
- -Note 1: Some questions have a default answer, which is indicated inside the -parenthesis. If you don't want to answer the question, just hit enter. It will -use the default answer. - -Note 2: As you see in the questions, you can initiate a git repository for the -new mode right away by answering `Y` (default) to the question. - -Note 3: Finally, as indicated by the green lines at the end, `create-mode` command only -create the mode template. You will need to link the mode to the Viewer in order -to use it. See the [`link-mode`](#link-mode) command. - -If we take a look at the directory that we created, we will see the following -files: - -
- -![image](../assets/img/mode-template.png) - -
- - -### create-extension - -Similar to the `create-extension` command, you can use the `create-extension` -command to create a new extension template. This command will create a new -extension template in the directory that you specify the path. - -```bash -yarn run cli create-extension -``` - - -Note: again similar to the `create-extension` command, you need to manually link -the extension to the Viewer in order to use it. See the -[`link-mode`](#link-mode) command. - - -### link-extension - -`link-extension` command will link a local OHIF extension to the Viewer. This -command will utilize `yarn link` to achieve so. - -```bash -yarn run cli link-extension -``` - -### unlink-extension - -There might be situations where you want to unlink an extension from the Viewer -after some developments. `unlink-extension` command will do so. - -```bash -ohif-cli unlink-extension -``` - - - -### link-mode - -Similar to the `link-extension` command, `link-mode` command will link a local -OHIF mode to the Viewer. - -```bash -yarn run cli link-mode -``` - -### unlink-mode - -Similar to the `unlink-extension` command, `unlink-mode` command will unlink a -local OHIF mode from the Viewer. - -```bash -ohif-cli unlink-mode -``` - -### add-mode - -OHIF is a modular viewer. This means that you can install (add) different modes -to the viewer if they are published online . `add-mode` command will add a new mode to -the viewer. It will look for the mode in the NPM registry and installs it. This -command will also add the extension dependencies that the mode relies on to the -Viewer (if specified in the peerDependencies section of the package.json). - -:::note Important -`cli` will validate the npm package before adding it to the Viewer. An OHIF mode -should have `ohif-mode` as one of its keywords. -::: - -Note: If you don't specify the version, the latest version will be used. - -```bash -yarn run cli add-mode [version] -``` - -For instance `@ohif-test/mode-clock` is an example OHIF mode that we have -published to NPM. This mode basically has a panel that shows the clock :) - -We can add this mode to the Viewer by running the following command: - -```bash -yarn run cli add-mode @ohif-test/mode-clock -``` - -After installation, the Viewer has a new mode! - - -![image](../assets/img/add-mode.png) - - -Note: If the mode has an extension peerDependency (in this case @ohif-test/extension-clock), -`cli` will automatically add the extension to the Viewer too. - -The result - -![image](../assets/img/clock-mode.png) -![image](../assets/img/clock-mode1.png) - -### add-extension - -This command will add an OHIF extension to the Viewer. It will look for the -extension in the NPM registry and install it. - -```bash -yarn run cli add-extension [version] -``` - - -### remove-mode - -This command will remove the mode from the Viewer and also remove the extension -dependencies that the mode relies on from the Viewer. - -```bash -yarn run cli remove-mode -``` - - -### remove-extension - -Similar to the `remove-mode` command, this command will remove the extension -from the Viewer. - -```bash -yarn run cli remove-extension -``` - -### list - -`list` command will list all the installed extensions and modes in -the Viewer. It uses the `PluginConfig.json` file to list the installed -extensions and modes. - -```bash -yarn run cli list -``` - -an output would look like this: - -
- -![image](../assets/img/ohif-cli-list.png) - -
- -### search - -Using `search` command, you can search for OHIF extensions and modes -in the NPM registry. This tool can accept a `--verbose` flag to show more -information about the results. - -```bash -yarn run cli search [--verbose] -``` - -
- -![image](../assets/img/cli-search-no-verbose.png) - -
- -with the verbose flag `ohif-cli search --verbose` you will achieve the following -output: - -
- -![image](../assets/img/cli-search-with-verbose.png) - -
- - -## PluginConfig.json - -To make all the above commands work, we have created a new file called `PluginConfig.json` which contains the -information needed to run the commands. You **don't need to (and should not)** -edit/update/modify this file as it is automatically generated by the CLI. You -can take a look at what this file contains by going to -`platform/viewer/PluginConfig.json` in your project's root directory. In short, -this file tracks and stores all the extensions/modes and the their version that -are currently being used by the viewer. diff --git a/platform/docs/versioned_docs/version-3.0/development/our-process.md b/platform/docs/versioned_docs/version-3.0/development/our-process.md deleted file mode 100644 index d8b8ed86217..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/our-process.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Issue & PR Triage Process ---- - -# Our Process - -Our process is a living, breathing thing. We strive to have regular -[retrospectives][retrospective] that help us shape and adapt our process to our -team's current needs. This document attempts to capture the broad strokes of -that process in an effort to: - -- Strengthen community member involvement and understanding -- Welcome feedback and helpful suggestions - -## Issue Triage - -[GitHub issues][gh-issues] are the best way to provide feedback, ask questions, -and suggest changes to the OHIF Viewer's core team. Community issues generally -fall into one of three categories, and are marked with a `triage` label when -created. - -| Issue Template Name | Description | -| ---------------------- | ---------------------------------------------------------------------------------------- | -| Community: Report 🐛 | Describe a new issue; Provide steps to reproduce; Expected versus actual result? | -| Community: Request ✋ | Describe a proposed new feature. Why should it be implemented? What is the impact/value? | -| Community: Question ❓ | Seek clarification or assistance relevant to the repository. | - -_table 1. issue template names and descriptions_ - -Issues that require `triage` are akin to support tickets. As this is often our -first contact with would-be adopters and contributors, it's important that we -strive for timely responses and satisfactory resolutions. We attempt to -accomplish this by: - -1. Responding to issue requiring `triage` at least once a week -2. Create new "official issues" from "community issues" -3. Provide clear guidance and next steps (when applicable) -4. Regularly clean up old (stale) issues - -> 🖋 Less obviously, patterns in the issues being reported can highlight areas -> that need improvement. For example, users often have difficulty navigating -> CORS issues when deploying the OHIF Viewer -- how do we best reduce our ticket -> volume for this issue? - -### Backlogged Issues - -Community issues serve as vehicles of discussion that lead us to "backlogged -issues". Backlogged issues are the distilled and actionable information -extracted from community issues. They contain the scope and requirements -necessary for hand-off to a core-team (or community) contributor ^\_^ - -| Category | Description | Labels | -| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| Bugs | An issue with steps that produce a bug (an unexpected result). | [Bug: Verified 🐛][label-bug] | -| Stories | A feature/enhancement with a clear benefit, boundaries, and requirements. | [Story 🙌][label-story] | -| Tasks | Changes that improve [UX], [DX], or test coverage; but don't impact application behavior | [Task: CI/Tooling 🤖][label-tooling], [Task: Docs 📖][label-docs], [Task: Refactor 🛠][label-refactor], [Task: Tests 🔬][label-tests] | - -_table 2. backlogged issue types ([full list of labels][gh-labels])_ - -## Issue Curation (["backlog grooming"][groom-backlog]) - -If a [GitHub issue][gh-issues] has a `bug`, `story`, or `task` label; it's on -our backlog. If an issue is on our backlog, it means we are, at the very least, -committed to reviewing any community drafted Pull Requests to complete the -issue. If you're interested in seeing an issue completed but don't know where to -start, please don't hesitate to leave a comment! - -While we don't yet have a long-term or quarterly road map, we do regularly add -items to our ["Active Development" GitHub Project Board][gh-board]. Items on -this project board are either in active development by Core Team members, or -queued up for development as in-progress items are completed. - -> 🖋 Want to contribute but not sure where to start? Check out [Up for -> grabs][label-grabs] issues and our [Contributing -> documentation][contributing-docs] - -## Contributions (Pull Requests) - -Incoming Pull Requests (PRs) are triaged using the following labels. Code review -is performed on all PRs where the bug fix or added functionality is deemed -appropriate: - -| Labels | Description | -| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| **Classification** | | -| [PR: Bug Fix][label-bug] | Filed to address a Bug. | -| [PR: Draft][draft] | Filed to gather early feedback from the core team, but which is not intended for merging in the short term. | -| **Review Workflow** | | -| [PR: Awaiting Response 💬][awaiting-response] | The core team is waiting for additional information from the author. | -| [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. | -| [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. | -| **QA** | | -| [PR: Awaiting User Cases 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start | -| [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience | - -We rely on GitHub Checks and integrations with third party services to evaluate -changes in code quality and test coverage. Tests must pass and User cases must -be present (when applicable) before a PR can be merged to master, and code -quality and test coverage must not be changed by a significant margin. For some -repositories, visual screenshot-based tests are also included, and video -recordings of end-to-end tests are stored for later review. - -[You can read more about our continous integration efforts here](/development/continous-integration.md) - -## Releases - -Releases are made automatically based on the type of commits which have been -merged (major.minor.patch). Releases are automatically pushed to NPM. Release -notes are automatically generated. Users can subscribe to GitHub and NPM -releases. - -We host development, staging, and production environments for the Progressive -Web Application version of the OHIF Viewer. [Development][ohif-dev] always -reflects the latest changes on our master branch. [Staging][ohif-stage] is used -to regression test a release before a bi-weekly deploy to our [Production -environment][ohif-prod]. - -Important announcements are made on GitHub, tagged as Announcement, and pinned -so that they remain at the top of the Issue page. - -The Core team occasionally performs full manual testing to begin the process of -releasing a Stable version. Once testing is complete, the known issues are -addressed and a Stable version is released. - - - - -[groom-backlog]: https://www.agilealliance.org/glossary/backlog-grooming -[retrospective]: https://www.atlassian.com/team-playbook/plays/retrospective -[gh-issues]: https://github.com/OHIF/Viewers/issues/new/choose -[gh-labels]: https://github.com/OHIF/Viewers/labels - -[label-story]: https://github.com/OHIF/Viewers/labels/Story%20%3Araised_hands%3A -[label-tooling]: https://github.com/OHIF/Viewers/labels/Task%3A%20CI%2FTooling%20%3Arobot%3A -[label-docs]: https://github.com/OHIF/Viewers/labels/Task%3A%20Docs%20%3Abook%3A -[label-refactor]: https://github.com/OHIF/Viewers/labels/Task%3A%20Refactor%20%3Ahammer_and_wrench%3A -[label-tests]: https://github.com/OHIF/Viewers/labels/Task%3A%20Tests%20%3Amicroscope%3A -[label-bug]: https://github.com/OHIF/Viewers/labels/Bug%3A%20Verified%20%3Abug%3A - -[draft]: https://github.com/OHIF/Viewers/labels/PR%3A%20Draft -[awaiting-response]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Response%20%3Aspeech_balloon%3A -[awaiting-review]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Review%20%3Aeyes%3A -[awaiting-stories]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20UX%20Stories%20%3Adancer%3A -[awaiting-revisions]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Revisions%20%3Apen%3A -[no-ux-impact]: https://github.com/OHIF/Viewers/labels/PR%3A%20No%20UX%20Impact%20%3Aupside_down_face%3A - -[ohif-dev]: https://viewer-dev.ohif.org -[ohif-stage]: https://viewer-stage.ohif.org -[ohif-prod]: https://viewer.ohif.org -[gh-board]: https://github.com/OHIF/Viewers/projects/4 -[label-grabs]: https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs+%3Araising_hand_woman%3A%22 -[contributing-docs]: ./development/contributing.md - diff --git a/platform/docs/versioned_docs/version-3.0/development/testing.md b/platform/docs/versioned_docs/version-3.0/development/testing.md deleted file mode 100644 index c7c846aaa3d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/development/testing.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Testing ---- - -# Running Tests for OHIF - -We introduce here various test types that is available for OHIF, and how to run -each test in order to make sure your contribution hasn't broken any existing -functionalities. Idea and philosophy of each testing category is discussed in -the second part of this page. - -## Unit test - -To run the unit test: - -```bash -yarn run test:unit:ci -``` - -Note: You should have already installed all the packages with `yarn install`. - -Running unit test will generate a report at the end showing the successful and -unsuccessful tests with detailed explanations. - -## End-to-end test -For running the OHIF e2e test you need to run the following steps: - -- Open a new terminal, and from the root of the OHIF mono repo, run the following command: - - ```bash - yarn test:data - ``` - - This will download the required data to run the e2e tests (it might take a while). - The `test:data` only needs to be run once and checks the data out. Read more about - test data [below](#test-data). - -- Run the viewer with e2e config - - ```bash - APP_CONFIG=config/e2e.js yarn start - ``` - - You should be able to see test studies in the study list - - ![OHIF-e2e-test-studies](../assets/img/OHIF-e2e-test-studies.png) - -- Open a new terminal inside the OHIF project, and run the e2e cypress test - - ```bash - yarn test:e2e - ``` - - You should be able to see the cypress window open - - ![e2e-cypress](../assets/img/e2e-cypress.png) - - Run the tests by clicking on the `Run #number integration tests` . - - A new window will open, and you will see e2e tests being executed one after - each other. - - ![e2e-cypress-final](../assets/img/e2e-cypress-final.png) - - ## Test Data - The testing data is stored in two OHIF repositories. The first contains the - binary DICOM data, at [viewer-testdata](https://github.com/OHIF/viewer-testdata.git) - while the second module contains data in the DICOMweb format, installed as a submodule - into OHIF in the `testdata` directory. This is retrieved via the command - ```bash - yarn test:data - ``` - or the equivalent command `git submodule update --init` - When adding new data, run: - ``` - npm install -g dicomp10-to-dicomweb - mkdicomweb -d dicomweb dcm - ``` - to update the local dicomweb submodule in viewer-testdata. Then, commit - that data and update the submodules used in OHIF and in the viewer-testdata - parent modules. - - All data MUST be fully anonymized and allowed to be used for open access. - Any attributions should be included in the DCM directory. - -## Testing Philosophy - -> Testing is an opinionated topic. Here is a rough overview of our testing -> philosophy. See something you want to discuss or think should be changed? Open -> a PR and let's discuss. - -You're an engineer. You know how to write code, and writing tests isn't all that -different. But do you know why we write tests? Do you know when to write one, or -what kind of test to write? How do you know if a test is a _"good"_ test? This -document's goal is to give you the tools you need to make those determinations. - -Okay. So why do we write tests? To increase our... **CONFIDENCE** - -- If I do a large refactor, does everything still work? -- If I changed some critical piece of code, is it safe to push to production? - -Gaining the confidence we need to answer these questions after every change is -costly. Good tests allow us to answer them without manual regression testing. -What and how we choose to test to increase that confidence is nuanced. - -## Further Reading: Kinds of Tests - -Test's buy us confidence, but not all tests are created equal. Each kind of test -has a different cost to write and maintain. An expensive test is worth it if it -gives us confidence that a payment is processed, but it may not be the best -choice for asserting an element's border color. - -| Test Type | Example | Speed | Cost | -| ----------- | ------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------ | -| Static | `addNums(1, '2')` called with `string`, expected `int`. | :rocket: Instant | :money_with_wings: | -| Unit | `addNums(1, 2)` returns expected result `3` | :airplane: Fast | :money_with_wings::money_with_wings: | -| Integration | Clicking "Sign In", navigates to the dashboard (mocked network requests) | :running: Okay | :money_with_wings::money_with_wings::money_with_wings: | -| End-to-end | Clicking "Sign In", navigates to the dashboard (no mocks) | :turtle: Slow | :money_with_wings::money_with_wings::money_with_wings::money_with_wings: | - -- :rocket: Speed: How quickly tests run -- :money_with_wings: Cost: Time to write, and to debug when broken (more points - of failure) - -### Static Code Analysis - -Modern tooling gives us this "for free". It can catch invalid regular -expressions, unused variables, and guarantee we're calling methods/functions -with the expected parameter types. - -Example Tooling: - -- [ESLint][eslint-rules] -- [TypeScript][typescript-docs] or [Flow][flow-org] - -### Unit Tests - -The building blocks of our libraries and applications. For these, you'll often -be testing a single function or method. Conceptually, this equates to: - -_Pure Function Test:_ - -- If I call `sum(2, 2)`, I expect the output to be `4` - -_Side Effect Test:_ - -- If I call `resetViewport(viewport)`, I expect `cornerstone.reset` to be called - with `viewport` - -#### When to use - -Anything that is exposed as public API should have unit tests. - -#### When to avoid - -You're actually testing implementation details. You're testing implementation -details if: - -- Your test does something that the consumer of your code would never do. - - IE. Using a private function -- A refactor can break your tests - -### Integration Tests - -We write integration tests to gain confidence that several units work together. -Generally, we want to mock as little as possible for these tests. In practice, -this means only mocking network requests. - -### End-to-End Tests - -These are the most expensive tests to write and maintain. Largely because, when -they fail, they have the largest number of potential points of failure. So why -do we write them? Because they also buy us the most confidence. - -#### When to use - -Mission critical features and functionality, or to cover a large breadth of -functionality until unit tests catch up. Unsure if we should have a test for -feature `X` or scenario `Y`? Open an issue and let's discuss. - -### General - -- [Assert(js) Conf 2018 Talks][assert-js-talks] - - [Write tests. Not too many. Mostly integration.][kent-talk] - Kent C. Dodds - - [I see your point, but…][gleb-talk] - Gleb Bahmutov -- [Static vs Unit vs Integration vs E2E Testing][kent-blog] - Kent C. Dodds - (Blog) - -### End-to-end Testing w/ Cypress - -- [Getting Started](https://docs.cypress.io/guides/overview/why-cypress.html) - - Be sure to check out `Getting Started` and `Core Concepts` -- [Best Practices](https://docs.cypress.io/guides/references/best-practices.html) -- [Example Recipes](https://docs.cypress.io/examples/examples/recipes.html) - - - - -[eslint-rules]: https://eslint.org/docs/rules/ -[mini-pacs]: https://github.com/OHIF/viewer-testdata -[typescript-docs]: https://www.typescriptlang.org/docs/home.html -[flow-org]: https://flow.org/ - -[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw -[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE -[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk -[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests - -[testing-trophy]: https://twitter.com/kentcdodds/status/960723172591992832?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E960723172591992832&ref_url=https%3A%2F%2Fkentcdodds.com%2Fblog%2Fwrite-tests -[aaron-square]: https://twitter.com/Carofine247/status/966727489274961920 -[gleb-pyramid]: https://twitter.com/Carofine247/status/966764532046684160/photo/3 -[testing-pyramid]: https://dojo.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid -[testing-dorito]: https://twitter.com/denvercoder/status/960752578198843392 -[testing-dorito-img]: https://pbs.twimg.com/media/DVVHXycUMAAcN-F?format=jpg&name=4096x4096 - diff --git a/platform/docs/versioned_docs/version-3.0/faq.md b/platform/docs/versioned_docs/version-3.0/faq.md deleted file mode 100644 index 67b7d30b2e8..00000000000 --- a/platform/docs/versioned_docs/version-3.0/faq.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -sidebar_position: 8 -sidebar_label: FAQ ---- - -# Frequently Asked Questions - -## Index - -- [Report a bug][report-bug] -- [Request a feature][new-feature] -- [Commercial Support & Consulting][commercial-support] -- [Academic collaborations][academic] -- [FDA Clearance or CE Marking][fda-clearance] -- [HIPAA Compliance][hipaa] - -### How do I report a bug? - -Navigate to our [GitHub Repository][new-issue], and submit a new bug report. -Follow the steps outlined in the [Bug Report Template][bug-report-template]. - -### How can I request a new feature? - -At the moment we are in the process of defining our roadmap and will do our best -to communicate this to the community. If your requested feature is on the -roadmap, then it will most likely be built at some point. If it is not, you are -welcome to build it yourself and [contribute it](development/contributing.md). -If you have resources and would like to fund the development of a feature, -please [contact us](https://www.ohif.org) or work with community members that -offer [consulting services][commercial-support]. - -### Who should I contact about Academic Collaborations? - -[Gordon J. Harris](https://www.dfhcc.harvard.edu/insider/member-detail/member/gordon-j-harris-phd/) -at Massachusetts General Hospital is the primary contact for any academic -collaborators. We are always happy to hear about new groups interested in using -the OHIF framework, and may be able to provide development support if the -proposed collaboration has an impact on cancer research. - -### Does OHIF offer commercial support? - -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. - -### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? - -**NO.** The OHIF Viewer is **NOT** F.D.A. cleared or CE Marked. It is the users' -responsibility to ensure compliance with applicable rules and regulations. The -[License](https://github.com/OHIF/Viewers/blob/master/LICENSE) for the OHIF -Platform does not prevent your company or group from seeking F.D.A. clearance -for a product built using the platform. - -If you have gone this route (or are going there), please let us know because we -would be interested to hear about your experience. - -### Is The OHIF Viewer [HIPAA][hipaa-def] Compliant? - -**NO.** The OHIF Viewer **DOES NOT** fulfill all of the criteria to become HIPAA -Compliant. It is the users' responsibility to ensure compliance with applicable -rules and regulations. - - - - - -[report-bug]: #how-do-i-report-a-bug -[new-feature]: #how-can-i-request-a-new-feature -[commercial-support]: #does-ohif-offer-commercial-support -[academic]: #who-should-i-contact-about-academic-collaborations -[fda-clearance]: #does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission -[hipaa]: #is-the-ohif-viewer-hipaa-compliant - -[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/ -[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en -[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act -[new-issue]: https://github.com/OHIF/Viewers/issues/new/choose -[bug-report-template]: https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Bug+Report+%3Abug%3A&template=---bug-report.md&title= - diff --git a/platform/docs/versioned_docs/version-3.0/platform/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/_category_.json deleted file mode 100644 index 842e4abf4a1..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Platform", - "position": 6 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md b/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md deleted file mode 100644 index 4fd2691a4aa..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Environment Variables ---- -# Environment Variables - -There are a number of environment variables we use at build time to influence the output application's behavior. - -```bash -# Application -NODE_ENV=< production | development > -DEBUG=< true | false > -APP_CONFIG=< relative path to application configuration file > -PUBLIC_URL=<> -VERSION_NUMBER= -BUILD_NUM= -# i18n -USE_LOCIZE= -LOCIZE_PROJECTID= -LOCIZE_API_KEY= -``` - -## Setting Environment Variables - -- `npx cross-env` -- `.env` files -- env variables on build machine, or for terminal session diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json deleted file mode 100644 index b7a30d960fb..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Extensions", - "position": 9 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md deleted file mode 100644 index 148b82a9faf..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Extension Manager ---- - -# Extension Manager - -## Overview - -The `ExtensionManager` is a class made available to us via the `@ohif/core` -project (platform/core). Our application instantiates a single instance of it, -and provides a `ServicesManager` and `CommandsManager` along with the -application's configuration through the appConfig key (optional). - -```js -const commandsManager = new CommandsManager(); -const servicesManager = new ServicesManager(); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - appConfig, -}); -``` - -The `ExtensionManager` only has a few public members: - -- `setActiveDataSource` - Sets the active data source for the application -- `getDataSources` - Returns the registered data sources -- `getActiveDataSource` - Returns the currently active data source -- `getModuleEntry` - Returns the module entry by the give id. - -## Accessing Modules - -We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on -the provided IDs in the mode's configuration. - -For instance: -`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` -accesses the `seriesList` panel from `panelModule` of the -`@ohif/extension-measurement-tracking` extension. - -```js -const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md deleted file mode 100644 index b1920b52104..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Introduction - -We have re-designed the architecture of the `OHIF-v3` to enable building -applications that are easily extensible to various use cases (modes) that behind -the scene would utilize desired functionalities (extensions) to reach the goal -of the use case. - -Previously, extensions were “additive” and could not easily be mixed and matched -within the same viewer for different use cases. Previous `OHIF-v2` architecture -meant that any minor extension alteration usually would require the user to hard -fork. E.g. removing some tools from the toolbar of the cornerstone -extension meant you had to hard fork it, which was frustrating if the -implementation was otherwise the same as master. - -> - Developers should make packages of _reusable_ functionality as extensions, -> and can consume publicly available extensions. -> - Any conceivable radiological workflow or viewer setup will be able to be -> built with the platform through _modes_. - -Practical examples of extensions include: - -- A set of segmentation tools that build on top of the `cornerstone` viewport -- A set of rendering functionalities to volume render the data -- [See our maintained extensions for more examples of what's possible](#maintained-extensions) - -**Diagram showing how extensions are configured and accessed.** - - - -## Extension Skeleton - -An extension is a plain JavaScript object that has `id` and `version` properties, and one or -more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks). - -```js -// prettier-ignore -export default { - /** - * Required properties. Should be a unique value across all extensions. - */ - id, - - // Lifecyle - preRegistration() { /* */ }, - onModeEnter() { /* */ }, - onModeExit() { /* */ }, - // Modules - getLayoutTemplateModule() { /* */ }, - getDataSourcesModule() { /* */ }, - getSopClassHandlerModule() { /* */ }, - getPanelModule() { /* */ }, - getViewportModule() { /* */ }, - getCommandsModule() { /* */ }, - getContextModule() { /* */ }, - getToolbarModule() { /* */ }, - getHangingProtocolModule() { /* */ }, -} -``` - -## OHIF-Maintained Extensions - -A small number of powerful extensions for popular use cases are maintained by -OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in -the top level [`extensions/`][ext-source] directory. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ExtensionDescriptionModules
- - Default - - - Default extension provides default viewer layout, a study/series - browser, and a datasource that maps to a DICOMWeb compliant backend - commandsModule, ContextModule, DataSourceModule, HangingProtocolModule, LayoutTemplateModule, PanelModule, SOPClassHandlerModule, ToolbarModule
- - Cornerstone - - - Provides rendering functionalities for 2D images. - ViewportModule, CommandsModule
- DICOM PDF - - Renders PDFs for a specific SopClassUID. - Viewport, SopClassHandler
- DICOM SR - - Maintained extensions for cornerstone and visualization of DICOM Structured Reports - ViewportModule, CommandsModule, SOPClassHandlerModule
- Measurement tracking - - Tracking measurements in the measurement panel - ContextModule,PanelModule,ViewportModule,CommandsModule
- -## Registering of Extensions - -`viewer` starts by registering all the extensions specified inside the -`pluginConfig.json`, by default we register all extensions in the repo. - - -```js title=platform/viewer/pluginConfig.json -// Simplified version of the `pluginConfig.json` file -{ - "extensions": [ - { - "packageName": "@ohif/extension-cornerstone", - "version": "3.0.0" - }, - { - "packageName": "@ohif/extension-measurement-tracking", - "version": "3.0.0" - }, - // ... - ], - "modes": [ - { - "packageName": "@ohif/mode-longitudinal", - "version": "0.0.1" - } - ] -} -``` - -:::note Important -You SHOULD NOT directly register extensions in the `pluginConfig.json` file. -Use the provided `cli` to add/remove/install/uninstall extensions. Read more [here](../../development/ohif-cli.md) -::: - -The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). - -After an extension gets registered withing the `viewer`, -each [module](#modules) defined by the extension becomes available to the modes -via the `ExtensionManager` by requesting it via its id. -[Read more about Extension Manager](#extension-manager) - -## Lifecycle Hooks - -Currently, there are three lifecycle hook for extensions: - -[`preRegistration`](./lifecycle/#preRegistration) This hook is called once on -initialization of the entire viewer application, used to initialize the -extensions state, and consume user defined extension configuration. If an -extension defines the [`preRegistration`](./lifecycle/#preRegistration) -lifecycle hook, it is called before any modules are registered in the -`ExtensionManager`. It's most commonly used to wire up extensions to -[services](./../services/index.md) and [commands](./modules/commands.md), and to -bootstrap 3rd party libraries. - -[`onModeEnter`](./lifecycle#onModeEnter): This hook is called whenever a new -mode is entered, or a mode’s data or datasource is switched. This hook can be -used to initialize data. - -[`onModeExit`](./lifecycle#onModeExit): Similarly to onModeEnter, this hook is -called when navigating away from a mode, or before a mode’s data or datasource -is changed. This can be used to clean up data (e.g. remove annotations that do -not need to be persisted) - -## Modules - -Modules are the meat of extensions, the `blocks` that we have been talking about -a lot. They provide "definitions", components, and filtering/mapping logic that -are then made available to modes and services. - -Each module type has a special purpose, and is consumed by our viewer -differently. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Types - Description
- - LayoutTemplate (NEW) - - Control Layout of a route
- - DataSource (NEW) - - Control the mapping from DICOM metadata to OHIF-metadata
- - SOPClassHandler - - Determines how retrieved study data is split into "DisplaySets"
- - Panel - - Adds left or right hand side panels
- - Viewport - - Adds a component responsible for rendering a "DisplaySet"
- - Commands - - Adds named commands, scoped to a context, to the CommandsManager
- - Toolbar - - Adds buttons or custom components to the toolbar
- - Context - - Shared state for a workflow or set of extension module definitions
- - HangingProtocol - - Adds hanging protocol rules
- -Tbl. Module types -with abridged descriptions and examples. Each module links to a dedicated -documentation page. - -### Contexts - -The `@ohif/viewer` tracks "active contexts" that extensions can use to scope -their functionality. Some example contexts being: - -- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST` -- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK` - -An extension module can use these to say "Only show this Toolbar Button if the -active viewport is a Cornerstone viewport." This helps us use the appropriate UI -and behaviors depending on the current contexts. - -For example, if we have hotkey that "rotates the active viewport", each Viewport -module that supports this behavior can add a command with the same name, scoped -to the appropriate context. When the `command` is fired, the "active contexts" -are used to determine the appropriate implementation of the rotation behavior. - - - - -[viewers-repo]: https://github.com/OHIF/Viewers -[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions -[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js - diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md deleted file mode 100644 index 2e5fb81c2a1..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Installation ---- - -# Extension: Installation - -OHIF-v3 provides the ability to utilize external extensions. - - -You can use ohif `cli` tool to install both local and publicly published -extensions on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md deleted file mode 100644 index 422bce794cd..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Lifecycle Hooks ---- - -# Extensions: Lifecycle Hooks - -## Overview - -Extensions can implement specific lifecycle methods. - -- preRegistration -- onModeEnter -- onModeExit - -## preRegistration - -If an extension defines the `preRegistration` lifecycle hook, it is called -before any modules are registered in the `ExtensionManager`. This hook can be -used to: - -- initialize 3rd party libraries -- register event listeners -- add or call services -- add or call commands - -The `preRegistration` hook receives an object containing the -`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any -`configuration` that was provided with the extension at time of registration. - -Example `preRegistration` implementation that register a new service and make it -available in the app. We will talk more in details for creating a new service -for `OHIF-v3`. - -```js -// new service inside new extension -import MyNewService from './MyNewService'; - -export default function MyNewServiceWithServices(serviceManager) { - return { - name: 'MyNewService', - create: ({ configuration = {} }) => { - return new MyNewService(serviceManager); - }, - }; -} -``` - -and - -```js -import MyNewService from './MyNewService' - -export default { - id, - - /** - * @param {object} params - * @param {object} params.configuration - * @param {ServicesManager} params.servicesManager - * @param {CommandsManager} params.commandsManager - * @returns void - */ - preRegistration({ servicesManager, commandsManager, configuration }) { - console.log('Wiring up important stuff.'); - - window.importantStuff = () => { - console.log(configuration); - }; - - console.log('Important stuff has been wired.'); - window.importantStuff(); - - // Registering new services - servicesManager.registerService(MyNewService(servicesManager)); - }, - }, -}; -``` - -## onModeEnter - -If an extension defines the `onModeEnter` lifecycle hook, it is called when a -new mode is enters, or a mode's data or datasource is switched. - -For instance, in DICOM structured report extension (`dicom-sr`), we are using -`onModeEnter` to re-create the displaySets after a new mode is entered. - -_Example `onModeEnter` hook implementation_ - -```js -export default { - id: '@ohif/extension-cornerstone-dicom-sr', - - onModeEnter({ servicesManager }) { - const { DisplaySetService } = servicesManager.services; - const displaySetCache = DisplaySetService.getDisplaySetCache(); - - const srDisplaySets = displaySetCache.filter( - ds => ds.SOPClassHandlerId === SOPClassHandlerId - ); - - srDisplaySets.forEach(ds => { - // New mode route, allow SRs to be hydrated again - ds.isHydrated = false; - }); - }, -}; -``` - -## onModeExit - -If an extension defines the `onModeExit` lifecycle hook, it is called when -navigating away from a mode. This hook can be used to clean up data tasks such -as unregistering services, removing annotations that do not need to be -persisted. - -_Example `onModeExit` hook implementation_ - -```js -export default { - id: 'myExampleExtension', - - onModeExit({ servicesManager, commandsManager }) { - myCacheService.purge(); - }, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json deleted file mode 100644 index c131ccdd7e3..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Modules", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md deleted file mode 100644 index b37202bf61e..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Commands ---- -# Module: Commands - - -## Overview -`CommandsModule` includes list of arbitrary functions. These may activate tools, communicate with a server, open a modal, etc. -The significant difference between `OHIF-v3` and `OHIF-v2` is that in `v3` a `mode` defines -its toolbar, and which commands each tool call is inside in its toolDefinition - -An extension can register a Commands Module by defining a `getCommandsModule` -method. The Commands Module allows us to register one or more commands scoped to -specific [contexts](./../index.md#contexts). Commands have several unique -characteristics that make them tremendously powerful: - -- Multiple implementations for the same command can be defined -- Only the correct command's implementation will be run, dependent on the - application's "context" -- Commands are used by hotkeys, toolbar buttons and render settings - -Here is a simple example commands module: - -```js -const getCommandsModule = () => ({ - definitions: { - exampleActionDef: { - commandFn: ({ param1 }) => { - console.log(`param1's value is: ${param1}`); - }, - // storeContexts: ['viewports'], - options: { param1: 'param1' }, - context: 'VIEWER', // optional - }, - }, - defaultContext: 'ACTIVE_VIEWPORT::DICOMSR', -}); -``` - - -Each definition returned by the Commands Module is registered to the -`ExtensionManager`'s `CommandsManager`. - -> `storeContexts` has been removed in `OHIF-v3` and now modules have access to all commands and services. This change enables support for user-registered services. - -## Command Definitions - -The command definition consists of a named command (`exampleActionDef` below) and a -`commandFn`. The command name is used to call the command, and the `commandFn` -is the "command" that is actioned. - -```js -exampleActionDef: { - commandFn: ({ param1, options }) => { }, - options: { param1: 'measurement' }, - context: 'DEFAULT', -} -``` - -| Property | Type | Description | -| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | -| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | -| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | - -## Command Behavior - - - -**If there are multiple valid commands for the application's active contexts** - -- What happens: all commands are run -- When to use: A `clearData` command that cleans up state for multiple - extensions - -**If no commands are valid for the application's active contexts** - -- What happens: a warning is printed to the console -- When to use: a `hotkey` (like "invert") that doesn't make sense for the - current viewport (PDF or HTML) - -## `CommandsManager` Public API - -If you would like to run a command in the consuming app or an extension, you can -use `CommandsManager.runCommand(commandName, options = {}, contextName)` - - -```js -// Returns all commands for a given context -commandsManager.getContext('string'); - -// Run a command, it will run all the `speak` commands in all contexts -commandsManager.runCommand('speak', { command: 'hello' }); - -// Run command, from Default context -commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); -``` - -The `ExtensionManager` handles registering commands and creating contexts, so -most consumer's won't need these methods. If you find yourself using these, ask -yourself "why can't I register these commands via an extension?" - -```js -// Used by the `ExtensionManager` to register new commands -commandsManager.registerCommand('context', 'name', commandDefinition); - -// Creates a new context; clears the context if it already exists -commandsManager.createContext('string'); -``` - -### Contexts - -It is up to the consuming application to define what contexts are possible, and -which ones are currently active. As extensions depend heavily on these, we will -likely publish guidance around creating contexts, and ways to override extension -defined contexts in the near future. If you would like to discuss potential -changes to how contexts work, please don't hesitate to create a new GitHub -issue. - -[Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md deleted file mode 100644 index 35de381a3da..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -sidebar_position: 9 -sidebar_label: Context ---- -# Module: Context - -## Overview -This new module type allows you to connect components via a shared context. You can create a context that two components, e.g. a viewport and a panel can use to synchronize and communicate. An extensive example of this can be seen in the longitudinal mode’s custom extensions. - - - -```jsx -const ExampleContext = React.createContext(); - -function ExampleContextProvider({ children }) { - return ( - - {children} - - ); -} - -const getContextModule = () => [ - { - name: 'ExampleContext', - context: ExampleContext, - provider: ExampleContextProvider, - }, -]; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md deleted file mode 100644 index 381d7ea7e1e..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Data Source ---- - -# Module: Data Source - -## Overview - -The internal data structure of OHIF’s metadata follows naturalized DICOM JSON, A -format pioneered by `dcmjs`. In short DICOM metadata headers with DICOM Keywords -instead of tags and sequences as arrays, for easy development and clear code. - -We have built a standard for fetching and mapping data into OHIF’s native -format, which we call DataSources, and have provided one implementation of this -standard. - -You can make another datasource implementation which communicates to your -backend and maps to OHIF’s native format, then use any existing mode on your -platform. Your data doesn’t even need to be DICOM if you can map some -proprietary data to the correct format. - -The DataSource is also a place to add easy helper methods that platform-specific -extensions can call in order to interact with the backend, meaning proprietary -data interactions can be wrapped in extensions. - -```js -const getDataSourcesModule = () => [ - { - name: 'exampleDataSource', - type: 'webApi', // 'webApi' | 'local' | 'other' - createDataSource: dataSourceConfig => { - return IWebApiDataSource.create(/* */); - }, - }, -]; -``` - -Default extension provides two main data sources that are commonly used: -`dicomweb` and `dicomjson` - -```js -import { createDicomWebApi } from './DicomWebDataSource/index.js'; -import { createDicomJSONApi } from './DicomJSONDataSource/index.js'; - -function getDataSourcesModule() { - return [ - { - name: 'dicomweb', - type: 'webApi', - createDataSource: createDicomWebApi, - }, - { - name: 'dicomjson', - type: 'jsonApi', - createDataSource: createDicomJSONApi, - }, - ]; -} -``` - -## Custom DataSource - -You can add your custom datasource by creating the implementation using -`IWebApiDataSource.create` from `@ohif/core`. This factory function creates a -new "Web API" data source that fetches data over HTTP. - -You need to make sure, you implement the following functions for the data -source. - -```js title="platform/core/src/DataSources/IWebApiDataSource.js" -function create({ - query, - retrieve, - store, - reject, - parseRouteParams, - deleteStudyMetadataPromise, - getImageIdsForDisplaySet, - getImageIdsForInstance, -}) { - /* */ -} -``` - -You can take a look at `dicomweb` data source implementation to get an idea -`extensions/default/src/DicomWebDataSource/index.js` - -## Static WADO Client - -If the configuration for the data source has the value staticWado set, then it -is assumed that queries for the studies return a super-set of the studies, as it -is assumed to be returning a static list. The StaticWadoClient performs the -search functionality manually, by interpreting the query parameters and then -applying them to the returned response. This functionality may be useful for -other types of DICOMweb back ends, where they are capable of performing queries, -but don't allow for querying certain types of fields. However, that only works -as long as the size of the studies list isn't too large that client side -selection isn't too expensive. - -## DicomMetadataStore - -In `OHIF-v3` we have a central location for the metadata of studies, and they are -located in `DicomMetadataStore`. Your custom datasource can communicate with -`DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will -learn more about `DicomMetadataStore` in services. diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md deleted file mode 100644 index 649d6644eec..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -sidebar_position: 8 -sidebar_label: Hanging Protocol ---- -# Module: Hanging Protocol - -## Overview -`hangingProtocolModule` provides the protocols for hanging the displaySets in the viewer. -This module can be as simple as loading a list of pre-defined protocols, or it can be more complex -and `fetch` the protocols from a server. - -You can read more about hanging protocols in HangingProtocolService. - -```js -const deafultProtocol = { - id: 'defaultProtocol', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - stages: [ - { - id: 'nwzau7jDkEkL8djfr', - name: 'oneByOne', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - viewports: [ - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [], - studyMatchingRules: [], - }, - ], - createdDate: '2021-02-23T19:22:08.894Z', - }, - ], - numberOfPriorsReferenced: -1, -}; - -function getHangingProtocolModule() { - return [ - { - name: hangingProtocolName, - protocols: [deafultProtocol], - }, - ]; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md deleted file mode 100644 index 66c839dae1b..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: Layout Template ---- - -# Module: Layout Template - -## Overview - -`LayoutTemplates` are a new concept in v3 that modes use to control the layout -of a route. A layout template is a React component that is given a set of -managers that define apis to access toolbar state, commands, and hotkeys, as -well as props defined by the layout template. - -For instance the default LayoutTemplate takes in leftPanels, rightPanels and -viewports as props, which it uses to build its view. - -In addition, `layout template` has complete control over the structure of the -application. You could have tools down the left side, or a strict guided -workflow with tools set programmatically, the choice is yours for your use case. - -```jsx -const getLayoutTemplateModule = (/* ... */) => [ - { - id: 'exampleLayout', - name: 'exampleLayout', - component: ExampleLayoutComponent, - }, -]; -``` - -The `props` that are passed to `layoutTemplate` are managers and service, along -with the defined mode left/right panels, mode's defined viewports and OHIF -`ViewportGridComp`. LayoutTemplate leverages extensionManager to grab typed -extension module entries: `*.getModuleEntry(id)` - -A simplified code for `Default extension`'s layout template is: - -```jsx title="extensions/default/src/ViewerLayout/index.jsx" -import React from 'react'; -import { SidePanel } from '@ohif/ui'; - -function Toolbar({ servicesManager }) { - const { ToolBarService } = servicesManager.services; - - return ( - <> - // ToolBarService.getButtonSection('primary') to get toolbarButtons - {toolbarButtons.map((toolDef, index) => { - const { id, Component, componentProps } = toolDef; - return ( - ToolBarService.recordInteraction(args)} - /> - ); - })} - - ); -} - -function ViewerLayout({ - // From Extension Module Params - extensionManager, - servicesManager, - hotkeysManager, - commandsManager, - // From Modes - leftPanels, - rightPanels, - viewports, - ViewportGridComp, -}) { - const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; - }; - - const getViewportComponentData = viewportComponent => { - const entry = extensionManager.getModuleEntry(viewportComponent.namespace); - - return { - component: entry.component, - displaySetsToDisplay: viewportComponent.displaySetsToDisplay, - }; - }; - - const leftPanelComponents = leftPanels.map(getPanelData); - const rightPanelComponents = rightPanels.map(getPanelData); - const viewportComponents = viewports.map(getViewportComponentData); - - return ( -
- - -
- {/* LEFT SIDEPANELS */} - - - {/* TOOLBAR + GRID */} - - - {/* Rigth SIDEPANELS */} - -
-
- ); -} -``` - -## Overview Video - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md deleted file mode 100644 index 1ad0d8c0813..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Panel ---- - -# Module: Panel - -## Overview - -The default LayoutTemplate has panels on the left and right sides, however one -could make a template with panels at the top or bottom and make extensions with -panels intended for such slots. - -An extension can register a Panel Module by defining a `getPanelModule` method. -The panel module provides the ability to define `menuOptions` and `components` -that can be used by the consuming application. `components` are React Components -that can be displayed in the consuming application's "Panel" Component. - -![panel-module-v3](../../../assets/img/panel-module-v3.png) - -The `menuOptions`'s `target` key, points to a registered `components`'s `id`. A -`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can -optionally provide its own `context` value. - -The `getPanelModule` receives an object containing the `ExtensionManager`'s -associated `ServicesManager` and `CommandsManager`. - -```jsx -import PanelMeasurementTable from './PanelMeasurementTable.js'; - -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { - const wrappedMeasurementPanel = () => { - return ( - - ); - }; - - return [ - { - name: 'measure', - iconName: 'list-bullets', - iconLabel: 'Measure', - label: 'Measurements', - isDisabled: studies => {}, // optional - component: wrappedMeasurementPanel, - }, - ]; -} -``` - -## Consuming Panels Inside Modes - -As explained earlier, extensions make the functionalities and components -available and `modes` utilize them to build an app. So, as seen above, we are -not actually defining which side the panel should be opened. Our extension is -providing the component with its. - -New: You can easily add multiple panels to the left/right side of the viewer -using the mode configuration. As seen below, the `leftPanels` and `rightPanels` -accept an `Array` of the `IDs`. - -```js - -const extensionDependencies = { - '@ohif/extension-default': '^3.0.0', - '@ohif/extension-cornerstone': '^3.0.0', - '@ohif/extension-measurement-tracking': '^3.0.0', - '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', -}; - -const id = 'viewer' -const version = '3.0.0 - -function modeFactory({ modeConfiguration }) { - return { - id, - routes: [ - { - path: 'longitudinal', - layoutTemplate: ({ location, servicesManager }) => { - return { - id, - props: { - leftPanels: [ - '@ohif/extension-measurement-tracking.panelModule.seriesList', - ], - rightPanels: [ - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', - ], - viewports, - }, - }; - }, - }, - ], - extensions: extensionDependencies - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -}; - -export default mode; - -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md deleted file mode 100644 index eb1b7a46410..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: SOP Class Handler ---- -# Module: SOP Class Handler - -## Overview -This module defines how a specific DICOM SOP class should be processed to make a displaySet, something that can be hung in a viewport. An extension can register a [SOP Class][sop-class-link] Handler Module by defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]. - -The mode chooses what SOPClassHandlers to use, so you could process a series in a different way depending on mode within the same application. - - -SOPClassHandler is a bit different from the other modules, as it doesn't provide a `1:1` -schema for UI or provide its own components. It instead defines: - -- `sopClassUIDs`: an array of string SOP Class UIDs that the - `getDisplaySetFromSeries` method should be applied to. -- `getDisplaySetFromSeries`: a method that maps series and study metadata to a - display set - -A `displaySet` has the following shape: - -```js -return { - Modality: 'MR', - displaySetInstanceUIDD - SeriesDate, - SeriesTime, - SeriesInstanceUID, - StudyInstanceUID, - SeriesNumber, - FrameRate, - SeriesDescription, - isMultiFrame, - numImageFrames, - SOPClassHandlerId, -} -``` - -## Example SOP Class Handler Module - -```js -import ImageSet from '@ohif/core/src/classes/ImageSet'; - - -const sopClassDictionary = { - CTImageStorage: "1.2.840.10008.5.1.4.1.1.2", - MRImageStorage: "1.2.840.10008.5.1.4.1.1.4", -}; - - -// It is important to note that the used SOPClassUIDs in the modes are in the order that is specified in the array. -const sopClassUids = [ - sopClassDictionary.CTImageStorage, - sopClassDictionary.MRImageStorage, -; - -const makeDisplaySet = (instances) => { - const instance = instances[0]; - const imageSet = new ImageSet(instances); - - imageSet.setAttributes({ - displaySetInstanceUID: imageSet.uid, - SeriesDate: instance.SeriesDate, - SeriesTime: instance.SeriesTime, - SeriesInstanceUID: instance.SeriesInstanceUID, - StudyInstanceUID: instance.StudyInstanceUID, - SeriesNumber: instance.SeriesNumber, - FrameRate: instance.FrameTime, - SeriesDescription: instance.SeriesDescription, - Modality: instance.Modality, - isMultiFrame: isMultiFrame(instance), - numImageFrames: instances.length, - SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, - }); - - return imageSet; -}; - -getSopClassHandlerModule = () => { - return [ - { - name: 'stack, - sopClassUids, - getDisplaySetsFromSeries: makeDisplaySet, - }, - ]; -}; - -``` - -### More examples : -You can find another example for this mapping between raw metadata and displaySet for -`DICOM-SR` extension. - -## `@ohif/viewer` usage - -We use the `sopClassHandlerModule`s in `DisplaySetService` where we -transform instances from the raw metadata format to a OHIF displaySet format. -You can read more about DisplaySetService here. - - -[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html -[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12 -[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6 -[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7 -[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7 - diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md deleted file mode 100644 index 98e0d91a7a2..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Toolbar ---- - -# Module: Toolbar - -An extension can register a Toolbar Module by defining a `getToolbarModule` -method. `OHIF-v3`'s `default` extension (`"@ohif/extension-default"`) provides 5 main -toolbar button types: - -![toolbarModule](../../../assets/img/toolbar-module.png) - -## Example Toolbar Module - -The Toolbar Module should return an array of `objects`. There are currently a -few different variations of definitions, each one is detailed further down. - -```js -export default function getToolbarModule({ commandsManager, servicesManager }) { - return [ - { - name: 'ohif.divider', - defaultComponent: ToolbarDivider, - clickHandler: () => {}, - }, - { - name: 'ohif.action', - defaultComponent: ToolbarButton, - clickHandler: () => {}, - }, - { - name: 'ohif.radioGroup', - defaultComponent: ToolbarButton, - clickHandler: () => {}, - }, - { - name: 'ohif.splitButton', - defaultComponent: ToolbarSplitButton, - clickHandler: () => {}, - }, - { - name: 'ohif.layoutSelector', - defaultComponent: ToolbarLayoutSelector, - clickHandler: (evt, clickedBtn, btnSectionName) => {}, - }, - ]; -} -``` - -## Toolbar buttons consumed in modes - -Below we can see a simplified version of the `longitudinal` mode that shows how -a mode can add buttons to the toolbar by calling -`ToolBarService.addButtons(toolbarButtons)`. `toolbarButtons` is an array of -`toolDefinitions` which we will learn next. - -```js -function modeFactory({ modeConfiguration }) { - return { - id: 'viewer', - displayName: 'Basic Viewer', - - onModeEnter: ({ servicesManager, extensionManager }) => { - const { ToolBarService } = servicesManager.services; - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - }, - routes: [ - { - path: 'longitudinal', - layoutTemplate: ({ location, servicesManager }) => { - return { - /* */ - }; - }, - }, - ], - }; -} -``` - -## Button Definitions - -The simplest toolbarButtons definition has the following properties: - -![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) - -```js -{ - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, -``` - -| property | description | values | -| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | -| `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | -| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | - -There are three main types of toolbar buttons: - -- `tool`: buttons that enable a tool by running the `setToolActive` command with - the `commandOptions` -- `toggle`: buttons that acts as a toggle: e.g., linking viewports -- `action`: buttons that executes an action: e.g., capture button to save - screenshot - -## Nested Buttons - -You can use the `ohif.splitButton` type to build a button with extra tools in -the dropdown. - -- First you need to give your `primary` tool definition to the split button -- the `secondary` properties can be a simple arrow down (`chevron-down` icon) -- For adding the extra tools add them to the `items` list. - -You can see below how `longitudinal` mode is using the available toolbarModule -to create `MeasurementTools` nested button - -![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) - -```js title="modes/longitudinal/src/toolbarButtons.js" -{ - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', - }, - items: [ - // Length tool - { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - // Bidirectional tool - { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } - }, - // Ellipse tool - { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } - }, - // Circle tool - { - id: 'CircleROI', - icon: 'tool-circle', - label: 'Circle', - type: 'tool', - commandOptions: { - toolName: 'CircleROI', - } - }, - ], - }, -} -``` - -
- -
- -## Layout Template - -Layout selector button and logic is also provided by the OHIF-v3 `default` -extension. To use it, you can just add the following definition to the list of -`toolDefinitions` - -![toolbarModule-layout](../../../assets/img/toolbarModule-layout.png) - -```js -{ - id: 'Layout', - type: 'ohif.layoutSelector', -} -``` - -
- -
- -## Custom Button - -You can also create your own extension, and add your new custom tool appearance -(e.g., split horizontally instead of vertically for split tool). Simply add -`getToolbarModule` to your extension, and pass your tool react component to its -`defaultComponent` property in the returned object. You can use `@ohif/ui` -components such as `IconButton, Icon, Tooltip, ToolbarButton` to build your own -component. - -```js -import myToolComponent from './myToolComponent'; - -export default function getToolbarModule({ commandsManager, servicesManager }) { - return [ - { - name: 'new-tool-type', - defaultComponent: myToolComponent, - clickHandler: () => {}, - }, - ]; -} -``` - -## Custom tool - -**I want to create a new tool** diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md deleted file mode 100644 index c3e07aa3464..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Viewport ---- - -# Module: Viewport - -## Overview - -Viewports consume a displaySet and display/allow the user to interact with data. -An extension can register a Viewport Module by defining a `getViewportModule` -method that returns a React component. Currently, we use viewport components to -add support for: - -- 2D Medical Image Viewing (cornerstone ext.) -- Structured Reports as SR (DICOM SR ext.) -- Structured Reports as HTML (DICOM html ext.) -- Encapsulated PDFs as PDFs (DICOM pdf ext.) -- Whole Slide Microscopy Viewing (whole slide ext.) -- etc. - -The general pattern is that a mode can define which `Viewport` to use for which -specific `SOPClassHandlerUID`, so if you want to fork just a single Viewport -component for a specialized mode, this is possible. - -```jsx -// displaySet, viewportIndex, dataSource -const getViewportModule = () => { - const wrappedViewport = props => { - return ( - { - commandsManager.runCommand('commandName', data); - }} - /> - ); - }; - - return [{ name: 'example', component: wrappedViewport }]; -}; -``` - -## Example Viewport Component - -A simplified version of the tracked CornerstoneViewport is shown below, which -creates a cornerstone viewport and action bar on top of it. - -```jsx -function TrackedCornerstoneViewport({ - children, - dataSource, - displaySet, - viewportIndex, - servicesManager, - extensionManager, - commandsManager, -}) { - const renderViewport = () => { - const { component: Component } = extensionManager.getModuleEntry( - '@ohif/extension-cornerstone.viewportModule.cornerstone' - ); - return ( - - ); - }; - - return ( - <> - -
- {renderViewport()} -
- - ); -} -``` - -![viewportModule](../../../assets/img/viewportModule.png) - -### `@ohif/viewer` - -Viewport components are managed by the `ViewportGrid` Component. Which Viewport -component is used depends on: - -- Hanging Protocols -- The Layout Configuration -- Registered SopClassHandlers - -![viewportModule-layout](../../../assets/img/viewportModule-layout.png) - -
An example of three cornerstone Viewports
diff --git a/platform/docs/versioned_docs/version-3.0/platform/internationalization.md b/platform/docs/versioned_docs/version-3.0/platform/internationalization.md deleted file mode 100644 index 1bb32de5ba5..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/internationalization.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Internationalization ---- - -# Viewer: Internationalization - -OHIF supports internationalization using [i18next](https://www.i18next.com/) -through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n), -where is the main instance of i18n containing several languages and tools. - -
-

Our translation management is powered by - Locize - through their generous support of open source.

- - Locize Translation Management Logo - -
- -## How to change language for the viewer? - -You can take a look into user manuals to see how to change the viewer's -language. In summary, you can change the language: - -- In the preference modals -- Using the language query in the URL: `lng=Test-LNG` - -## Installing - -```bash -yarn add @ohif/i18n - -# OR - -npm install --save @ohif/i18n -``` - -## How it works - -After installing `@ohif/i18n` npm package, the translation function -[t](https://www.i18next.com/overview/api#t) can be used [with](#with-react) or -[without](#without-react) React. - -A translation will occur every time a text match happens in a -[t](https://www.i18next.com/overview/api#t) function. - -The [t](https://www.i18next.com/overview/api#t) function is responsible for -getting translations using all the power of i18next. - -E.g. - -Before: - -```html -
my translated text
-``` - -After: - -```html -
{t('my translated text')}
-``` - -If the translation.json file contains a key that matches the HTML content e.g. -`my translated text`, it will be replaced automatically by the -[t](https://www.i18next.com/overview/api#t) function. - ---- - -### With React - -This section will introduce you to [react-i18next](https://react.i18next.com/) -basics and show how to implement the [t](https://www.i18next.com/overview/api#t) -function easily. - -#### Using Hooks - -You can use `useTranslation` hooks that is provided by `react-i18next` - -You can read more about this -[here](https://react.i18next.com/latest/usetranslation-hook). - -```js -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -function MyComponent() { - const { t } = useTranslation(); - - return

{t('my translated text')}

; -} -``` - -### Using outside of OHIF viewer - -OHIF Viewer already sets a main -[I18nextProvider](https://react.i18next.com/latest/i18nextprovider) connected to -the shared i18n instance from `@ohif/i18n`, all extensions inside OHIF Viewer -will share this same provider at the end, you don't need to set new providers at -all. - -But, if you need to use it completely outside of OHIF viewer, you can set the -I18nextProvider this way: - -```jsx -import i18n from '@ohif/i18n'; -import { I18nextProvider } from 'react-i18next'; -import App from './App'; - - - -; -``` - -After setting `I18nextProvider` in your React App, all translations from -`@ohif/i18n` should be available following the basic [With React](#with-react) -usage. - ---- - -### Without React - -When needed, you can also use available translations _without React_. - -E.g. - -```js -import { T } from '@ohif/i18n'; -console.log(T('my translated text')); -console.log(T('$t(Common:Play) my translated text')); -``` - ---- - -## Main Concepts While Translating - -## Namespaces - -Namespaces are being used to organize translations in smaller portions, combined -semantically or by use. Each `.json` file inside `@ohif/i18n` npm package -becomes a new namespace automatically. - -- Buttons: All buttons translations -- CineDialog: Translations for the tool tips inside the Cine Player Dialog -- Common: all common jargons that can be reused like `t('$t(common:image)')` -- Header: translations related to OHIF's Header Top Bar -- MeasurementTable - Translations for the `@ohif/ui` Measurement Table -- UserPreferencesModal - Translations for the `@ohif/ui` Preferences Modal -- Modals - Translations available for other modals -- PatientInfo - Translations for patients info hover -- SidePanel - Translations for side panels -- ToolTip - Translations for tool tips - -### How to use another NameSpace inside the current NameSpace? - -i18next provides a parsing feature able to get translations strings from any -NameSpace, like this following example getting data from `Common` NameSpace: - -``` -$t('Common:Reset') -``` - -## Extending Languages in @ohif/i18n - -Sometimes, even using the same language, some nouns or jargons can change -according to the country, states or even from Hospital to Hospital. - -In this cases, you don't need to set an entire language again, you can extend -languages creating a new folder inside a pre existent language folder and -@ohif/i18n will do the hard work. - -This new folder must to be called with a double character name, like the `UK` in -the following file tree: - -```bash - |-- src - |-- locales - index.js - |-- en - |-- Buttons.json - index.js - | UK - |-- Buttons.js - indes.js - | US - |-- Buttons.js - index.js - ... -``` - -All properties inside a Namespace will be merged in the new sub language, e.g -`en-US` and `en-UK` will merge the props with `en`, using i18next's fallback -languages tool. - -You will need to export all Json files in your `index.js` file, mounting an -object like this: - -```js - { - en: { - NameSpace: { - keyWord1: 'keyWord1Translation', - keyWord2: 'keyWord2Translation', - keyWord3: 'keyWord3Translation', - } - }, - 'en-UK': { - NameSpace: { - keyWord1: 'keyWord1DifferentTranslation', - } - } - } -``` - -Please check the `index.js` files inside locales folder for an example of this -exporting structure. - -### Extending languages dynamically - -You have access to the i18next instance, so you can use the -[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init) -method to add and change language resources as needed. - -E.g. - -```js -import { i18n } from '@ohif/i18n'; -i18next.addResourceBundle('pt-BR', 'Buttons', { - Angle: 'Ângulo', -}); -``` - ---- - -### How to set a whole new language - -To set a brand new language you can do it in two different ways: - -- Opening a pull request for `@ohif/i18n` and sharing the translation with the - community. 😍 Please see [Contributing](#contributing-with-new-languages) - section for further information. - -- Setting it only in your project or extension: - -You'll need a final object like the following, what is setting French as -language, and send it to `addLocales` method. - -```js -const newLanguage = - { - fr: { - Commons: { - "Reset": "Réinitialiser", - "Previous": "Précédent", - }, - Buttons: { - "Rectangle": "Rectangle", - "Circle": "Cercle", - } - } -``` - -To make it easier to translate, you can copy the .json files in the /locales -folder and theirs index.js exporters, keeping same keys and NameSpaces. -Importing the main index.js file, will provide you an Object as expected by the -method `addlocales`; - -E.g. of `addLocales` usage - -```js -import { addLocales } from '@ohif/i18n'; -import locales from './locales/index.js'; -addLocales(locales); -``` - -You can also set them manually, one by one, using this -[method](#extending-languages-dynamically). - ---- - -## Test Language - -We have created a test language that its translations can be seen in the locales -folder. You can copy paste the folder and its `.json` namespaces and add your -custom language translations. - -> If you apply the test-LNG you can see all the elements get appended with 'Test -> {}'. For instance `Study list` becomes `Test Study list`. - -## Language Detections - -@ohif/i18n uses -[i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) -to manage detections, also exports a method called initI18n that accepts a new -detector config as parameter. - -### Changing the language - -OHIF Viewer accepts a query param called `lng` in the url to change the -language. - -E.g. - -``` -https://docs.ohif.org/demo/?lng=es-MX -``` - -### Language Persistence - -The user's language preference is kept automatically by the detector and stored -at a cookie called 'i18next', and in a localstorage key called 'i18nextLng'. -These names can be changed with a new -[Detector Config](https://github.com/i18next/i18next-browser-languageDetector). - -## Debugging translations - -There is an environment variable responsible for debugging the translations, -called `REACT_APP_I18N_DEBUG`. - -Run the project as following to get full debug information: - -```bash -REACT_APP_I18N_DEBUG=true yarn run dev -``` - -## Contributing with new languages - -We have integrated `i18next` into the OHIF Viewer and hooked it up with Locize -for translation management. Now we need your help to get the app translated into -as many languages as possible, and ensure that we haven't missed pieces of the -app that need translation. Locize has graciously offered to provide us with free -usage of their product. - -Once each crowd-sourcing project is completed, we can approve it and merge the -changes into the main project. At that point, the language will be immediately -available on https://viewer.ohif.org/ for testing, and can be used in any OHIF -project. We will support usage through both the Locize CDN and by copying the -language directly into the `@ohif/i18n` package, so that end users can serve the -content from their own domains. - -Here are a couple examples: - -Spanish: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=es - -Chinese: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=zh - -Portugese: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=pt-BR - -Here are some links you can use to sign up to help translate. All you have to do -is sign up, translate the strings, and click Save. On our side, we have a -dashboard to see how many strings are translated and by whom. - -This is a pretty random set of languages, so please post below if you'd like a -new language link to be added: - -Languages: - -[French](https://www.locize.io/register?invitation=Nj8jRPaFKYwtIfNZ6Y5GVOJOpeiXNAdVuSiOg9ceaiveP6uF6y1wVXM9lgfKoYZX) - -[German](https://www.locize.io/register?invitation=gChNiVi66YINTPpbKESVAVYPapwg3DkpvMSSomLTvVqBJTXrdmPvxi0WZYHER11q) - -[Dutch](https://www.locize.io/register?invitation=2PGe7I184aN0cazM4GXMhzeLtGTf9Zen5uyOEFhHQ8vYkfKHkgR0mJ8dwbNlIeCG) - -[Turkish](https://www.locize.io/register?invitation=NOMIXsfneqPbFDqjce5wI7Z6p2swXSjc0rHOH4KLcM6qXSNA4LGyJaLxS7nqWAe3) - -[Chinese](https://www.locize.io/register?invitation=lrcUbt7DvV4aJmQeEA4SMAj5xNWr3rltOcaZW1cFc6eod0nvzSPFU4V383tDHGGn) - -[Japanese](https://www.locize.io/register?invitation=AaRq2S22o5FsxArwgVuw1gZcQjoe2ffyxarqlAXOpN7JnR2sf2mfamc5qV6LG1Mn) - -[Arabic](https://www.locize.io/register?invitation=BiqI6fOm1sC84N3YJLbImXmaOCk8Hc3TMGpXg7NH2R0b0OKuPCp9wlCHLoqMRpfQ) - -[Hindi](https://www.locize.io/register?invitation=ph7JmOGTV95DF3EFaI1kvK5Hx98dV9w2wj9h9UhUCWnkBNAwWEdWMcyjnF94zkWb) - -[Malay](https://www.locize.io/register?invitation=HsV9F5mKZyeUZYrC3XFRzNI2l0EsIh6hK0MUIKP8IYZA3GxuzfgkvWBLCFwCpDik) - -[Russian](https://www.locize.io/register?invitation=da4V9Q8DVO3M1FIlvfT50ZiS8NDNgvC0dE5hHUEAp47FXy6pLXmf1cp2lgLBfLmb) - -[Swedish](https://www.locize.io/register?invitation=uR4kzBZC1vhJe6jyMwYXgGPj84QDMulQRlt2s6rONU6ljUh5dgwuUyhJEtZ4REA3) - -[Italian](https://www.locize.io/register?invitation=viAS1NC5q342OxtuIv3JFX9DJ3KoR4SmGoElkBlRMphsDKt4hy9bW8JfBjHlfnd7) - -[Spanish](https://www.locize.io/register?invitation=ZikXW3KI4w4eo5Cf6L1aQMWaR69XAQ0a9Va3NGorH7mAPvEPXp8w8NLkPNLs5nG8) - -[Ukrainian](https://www.locize.io/register?invitation=TY0s6onqH3Asl05Bh1qB44SNSABL2pTYoturwxAmcNKRnzBZFK7bGfn7kVi23Vpg) - -[Vietnamese](https://www.locize.io/register?invitation=eqfHDm0vaqxGfQ5TGt6SeV0dx9b2dCp1RrMRdIRavqzOCOAfD3IElzUsyIT689cK) - -[Portugese-Brazil](https://www.locize.io/register?invitation=Qc5Dq449xbblQqLTpWeMfsyFiu3gACcgpj0EIucQjjs9Ph9pzPLpq3MnZupF9t6N) - -Don't see your language in the above list? Add a request -[here](https://github.com/OHIF/Viewers/issues/618) so that we can create the -language for your translation contribution. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json deleted file mode 100644 index 30940779278..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Managers", - "position": 11 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md b/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md deleted file mode 100644 index fab666dacad..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Commands Manager ---- -# Commands Manager - -## Overview - - -The `CommandsManager` is a class defined in the `@ohif/core` project. The Commands Manager tracks named commands (or functions) that are scoped to -a context. When we attempt to run a command with a given name, we look for it -in our active contexts. If found, we run the command, passing in any application -or call specific data specified in the command's definition. - -> Note: A single instance of `CommandsManager` should be defined in the consuming application, and it is used when constructing the `ExtensionManager`. - -A `simplified skeleton` of the `CommandsManager` is shown below: - -```js -export class CommandsManager { - constructor({ getActiveContexts } = {}) { - this.contexts = {}; - this._getActiveContexts = getActiveContexts; - } - - getContext(contextName) { - const context = this.contexts[contextName]; - return context; - } - - /**...**/ - - createContext(contextName) { - /** ... **/ - this.contexts[contextName] = {}; - } - - - registerCommand(contextName, commandName, definition) { - /**...**/ - const context = this.getContext(contextName); - /**...**/ - context[commandName] = definition; - } - - runCommand(commandName, options = {}, contextName) { - const definition = this.getCommand(commandName, contextName); - /**...**/ - const { commandFn } = definition; - const commandParams = Object.assign( - {}, - definition.options, // "Command configuration" - options // "Time of call" info - ); - /**...**/ - return commandFn(commandParams); - } - /**...**/ -} -``` - - - - -### Instantiating - -When we instantiate the `CommandsManager`, we are passing two methods: - -- `getAppState` - Should return the application's state when called (Not implemented in `v3`) -- `getActiveContexts` - Should return the application's active contexts when - called - -These methods are used internally to help determine which commands are currently -valid, and how to provide them with any state they may need at the time they are -called. - -```js title="platform/viewer/src/appInit.js" -const commandsManagerConfig = { - getAppState: () => {}, - /** Used by commands to determine active context */ - getActiveContexts: () => [ - 'VIEWER', - 'DEFAULT', - 'ACTIVE_VIEWPORT::CORNERSTONE', - ], -}; - -const commandsManager = new CommandsManager(commandsManagerConfig); -``` - - -## Commands/Context Registration -The `ExtensionManager` handles registering commands and creating contexts, so you don't need to register all your commands manually. Simply, create a `commandsModule` in your extension, and it will get automatically registered in the `context` provided. - -A *simplified version* of this registration is shown below to give an idea about the process. - - -```js -export default class ExtensionManager { - constructor({ commandsManager }) { - this._commandsManager = commandsManager - } - /** ... **/ - registerExtension = (extension, configuration = {}, dataSources = []) => { - let extensionId = extension.id - /** ... **/ - - // Register Modules provided by the extension - moduleTypeNames.forEach((moduleType) => { - const extensionModule = this._getExtensionModule( - moduleType, - extension, - extensionId, - configuration - ) - - if (moduleType === 'commandsModule') { - this._initCommandsModule(extensionModule) - } - /** registering other modules **/ - }) - } - - _initCommandsModule = (extensionModule) => { - let { definitions, defaultContext } = extensionModule - defaultContext = defaultContext || 'VIEWER' - - if (!this._commandsManager.getContext(defaultContext)) { - this._commandsManager.createContext(defaultContext) - } - - Object.keys(definitions).forEach((commandName) => { - const commandDefinition = definitions[commandName] - const commandHasContextThatDoesNotExist = - commandDefinition.context && - !this._commandsManager.getContext(commandDefinition.context) - - if (commandHasContextThatDoesNotExist) { - this._commandsManager.createContext(commandDefinition.context) - } - - this._commandsManager.registerCommand( - commandDefinition.context || defaultContext, - commandName, - commandDefinition - ) - }) - } -} - -``` - - -If you find yourself in a situation where you want to register a command/context manually, ask -yourself "why can't I register these commands via an extension?", but if you insist, you can use the `CommandsManager` API to do so: - -```js -// Command Registration -commandsManager.registerCommand('context', 'name', commandDefinition); - -// Context Creation -commandsManager.createContext('string'); -``` - -## `CommandsManager` Public API - -If you would like to run a command in the consuming app or an extension, you can -use `runCommand(commandName, options = {}, contextName)`. - - -```js -// Run a command, it will run all the `speak` commands in all contexts -commandsManager.runCommand('speak', { command: 'hello' }); - -// Run command, from Default context -commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); - -// Returns all commands for a given context -commandsManager.getContext('string'); -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md b/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md deleted file mode 100644 index 9fb5196f840..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Extension Manager ---- - -# Extension Manager - -## Overview - -The `ExtensionManager` is a class made available to us via the `@ohif/core` -project (platform/core). Our application instantiates a single instance of it, -and provides a `ServicesManager` and `CommandsManager` along with the -application's configuration through the appConfig key (optional). - -```js -const commandsManager = new CommandsManager(); -const servicesManager = new ServicesManager(); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - appConfig, -}); -``` - -The `ExtensionManager` only has a few public members: - -- `setActiveDataSource` - Sets the active data source for the application -- `getDataSources` - Returns the registered data sources -- `getActiveDataSource` - Returns the currently active data source -- `getModuleEntry` - Returns the module entry by the give id. - -## Accessing Modules - -We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on -the provided IDs in the mode's configuration. - -For instance: -`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` -accesses the `seriesList` panel from `panelModule` of the -`@ohif/extension-measurement-tracking` extension. - -```js -const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md b/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md deleted file mode 100644 index ebdd2bd1334..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Hotkeys Manager ---- -# Hotkeys Managers - -## Overview -`HotkeysManager` handles all the logics for adding, setting and enabling/disabling -the hotkeys. - - - -## Instantiation -`HotkeysManager` is instantiated in the `appInit` similar to the other managers. - -```js -const commandsManager = new CommandsManager(commandsManagerConfig); -const servicesManager = new ServicesManager(commandsManager); -const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - hotkeysManager, - appConfig, -}); -``` - - - - -## Hotkeys Manager API - -- `setHotkeys`: The most important method in the `HotkeysManager` which binds the keys with commands. -- `setDefaultHotKeys`: set the defaultHotkeys **property**. Note that, this method **does not** bind the provided hotkeys; however, when `restoreDefaultBindings` -is called, the provided defaultHotkeys will get bound. -- `destroy`: reset the HotkeysManager, and remove the set hotkeys and empty out the `defaultHotkeys` - - - -## Structure of a Hotkey Definition -A hotkey definition should have the following properties: - -- `commandName`: name of the registered command -- `commandOptions`: extra arguments to the commands -- `keys`: an array defining the key to get bound to the command -- `label`: label to be shown in the hotkeys preference panel -- `isEditable`: whether the key can be edited by the user in the hotkey panel - - -### Default hotkeysBindings -The default key bindings can be find in `hotkeyBindings.js` - -```js -// platform/core/src/defaults/hotkeyBindings.js - -export default [ - /**..**/ - { - commandName: 'setToolActive', - commandOptions: { toolName: 'Zoom' }, - label: 'Zoom', - keys: ['z'], - isEditable: true, - }, - - { - commandName: 'flipViewportHorizontal', - label: 'Flip Vertically', - keys: ['v'], - isEditable: true, - }, - /**..**/ -] -``` - - -## Behind the Scene -When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and -get run after the key is pressed. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/index.md b/platform/docs/versioned_docs/version-3.0/platform/managers/index.md deleted file mode 100644 index ee6d6b2fc8c..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/index.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Managers - -## Overview - -`OHIF` uses `Managers` to accomplish various purposes such as registering new -services, dependency injection, and aggregating and exposing `extension` -features. - -`OHIF-v3` provides the following managers which we will discuss in depth. - - - - - - - - - - - - - - - - - - - - - - - - - - -
ManagerDescription
- - Extension Manager - - - Aggregating and exposing modules and features through out the app -
- - Services Manager - - - Single point of registration for all internal and external services -
- - Commands Manager - - - Register commands with specific context and run commands in the app -
- - Hotkeys Manager - - - For keyboard keys assignment to commands -
- - - - - -[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services -[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js -[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern - diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/service.md b/platform/docs/versioned_docs/version-3.0/platform/managers/service.md deleted file mode 100644 index 2cacda412e8..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/service.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Service Manager ---- - -# Services Manager - -## Overview - -Services manager is the single point of service registration. Each service needs -to implement a `create` method which gets called inside `ServicesManager` to -instantiate the service. In the app, you can get access to a registered service -via the `services` property of the `ServicesManager`. - -## Skeleton - -_Simplified_ skeleton of `ServicesManager` is shown below. There are two public -methods: - -- `registerService`: registering a new service with/without a configuration -- `registerServices`: registering batch of services - -```js -export default class ServicesManager { - constructor(commandsManager) { - this._commandsManager = commandsManager; - this.services = {}; - this.registeredServiceNames = []; - } - - registerService(service, configuration = {}) { - /** validation checks **/ - this.services[service.name] = service.create({ - configuration, - commandsManager: this._commandsManager, - }); - - /* Track service registration */ - this.registeredServiceNames.push(service.name); - } - - registerServices(services) { - /** ... **/ - } -} -``` - -## Default Registered Services - -By default, `OHIF-v3` registers the following services in the `appInit`. - -```js title="platform/viewer/src/appInit.js" -servicesManager.registerServices([ - UINotificationService, - UIModalService, - UIDialogService, - UIViewportDialogService, - MeasurementService, - DisplaySetService, - ToolBarService, - ViewportGridService, - HangingProtocolService, - CineService, -]); -``` - -## Service Architecture - -If you take a look at the folder of each service implementation above, you will -find out that services need to be exported as an object with `name` and `create` -method. - -For instance, `ToolBarService` is exported as: - -```js title="platform/core/src/services/ToolBarService/index.js" -import ToolBarService from './ToolBarService'; - -export default { - name: 'ToolBarService', - create: ({ configuration = {}, commandsManager }) => { - return new ToolBarService(commandsManager); - }, -}; -``` - -and the implementation of `ToolBarService` lies in the same folder at -`./ToolbarSerivce.js`. - -> Note, the create method is critical for any custom service that you write and -> want to add to the list of services - -## Accessing Services - -Throughout the app you can use `services` property of the service manager to -access the desired service. - -For instance in the `PanelMeasurementTableTracking` which is the right panel in -the `longitudinal` mode, we have the _simplified code below_ for downloading the -drawn measurements. - -```js -function PanelMeasurementTableTracking({ servicesManager }) { - const { MeasurementService } = servicesManager.services; - /** ... **/ - - async function exportReport() { - const measurements = MeasurementService.getMeasurements(); - /** ... **/ - downloadCSVReport(measurements, MeasurementService); - } - - /** ... **/ - return <> /** ... **/ ; -} -``` - -## Registering Custom Services - -You might need to write you own custom service in an extension. -`preRegistration` hook inside your extension is the place for registering your -custom service. - -```js title="extensions/customExtension/src/index.js" -import WrappedBackEndService from './services/backEndService'; - -export default { - // ID of the extension - id: 'myExtension', - preRegistration({ servicesManager }) { - servicesManager.registerService(WrappedBackEndService(servicesManager)); - }, -}; -``` - -and the logic for your service shall be - -```js title="extensions/customExtension/src/services/backEndService/index.js" -import backEndService from './backEndService'; - -export default function WrappedBackEndService(serviceManager) { - return { - name: 'myService', - create: ({ configuration = {} }) => { - return new backEndService(serviceManager); - }, - }; -} -``` - -with implementation of - -```js -export default class backEndService { - constructor(serviceManager) { - this.serviceManager = serviceManager; - } - - putAnnotations() { - return post(/*...*/); - } -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json deleted file mode 100644 index 80702b9e54e..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Modes", - "position": 10 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/index.md b/platform/docs/versioned_docs/version-3.0/platform/modes/index.md deleted file mode 100644 index b01049b0f19..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/index.md +++ /dev/null @@ -1,383 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Modes - -## Overview - -A mode can be thought of as a viewer app configured to perform a specific task, -such as tracking measurements over time, 3D segmentation, a guided radiological -workflow, etc. Addition of modes enables _application_ with many _applications_ -as each mode become a mini _app configuration_ behind the scene. - -Upon initialization the viewer will consume extensions and modes and build up -the route desired, these can then be accessed via the study list, or directly -via url parameters. - - - -OHIF-v3 architecture can be seen in the following: - -![mode-archs](../../assets/img/mode-archs.png) - -> Note: Templates are now a part of “extensions” Routes are configured by modes -> and/or app - -As mentioned, modes are tied to a specific route in the viewer, and multiple -modes/routes can be present within a single application. This allows for -tremendously more flexibility than before you can now: - -- Simultaneously host multiple viewers with for different use cases from within - the same app deploy. -- Make radiological viewers for specific purposes/workflows, e.g.: - - Tracking the size of lesions over time. - - PET/CT fusion workflows. - - Guided review workflows optimized for a specific clinical trial. -- Still host one single feature-rich viewer if you desire. - -## Anatomy - -A mode configuration has a `route` name which is dynamically transformed into a -viewer route on initialization of the application. Modes that are available to a -study will appear in the study list. - -![user-study-summary](../../assets/img/user-study-summary.png) - -The mode configuration specifies which `extensions` the mode requires, which -`LayoutTemplate` to use, and what props to pass to the template. For the default -template this defines which `side panels` will be available, as well as what -`viewports` and which `displaySets` they may hang. - -Mode's config is composed of three elements: -- `id`: the mode `id` -- `modeFactory`: the function that returns the mode specific configuration -- `extensionDependencies`: the list of extensions that the mode requires - - -that return a config object with certain -properties, the high-level view of this config object is: - -```js title="modes/example/src/index.js" -function modeFactory() { - return { - id: '', - version: '', - displayName: '', - onModeEnter: () => {}, - onModeExit: () => {}, - validationTags: {}, - isValidMode: () => {}, - routes: [ - { - path: '', - init: () => {}, - layoutTemplate: () => {}, - }, - ], - extensions: extensionDependencies, - hangingProtocol: [], - sopClassHandlers: [], - hotkeys: [], - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -}; - -export default mode; -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyDescription
- id - unique mode id used to refer to the mode
- displayName - actual name of the mode being displayed for each study in the study summary panel
- - onModeEnter - - hook is called when the mode is entered by the specified route
- - onModeExit - - hook is called when the mode exited
- - validationTags - - validationTags
- - isValidMode - - Checks if the mode is valid for a study
- - routes - - route config which defines the route address, and the layout for it
- - extensionDependencies - - extensions needed by the mode
- - hanging protocol - - list of hanging protocols that the mode should have access to
- - sopClassHandlers - - list of SOPClass modules needed by the mode
- - hotkeys - - hotkeys
- -### Consuming Extensions - -As mentioned in the [Extensions](../extensions/index.md) section, in `OHIF-v3` -developers write their extensions to create re-usable functionalities that later -can be used by `modes`. Now, it is time to describe how the registered -extensions will get utilized for a workflow mode via its `id`. - -Each `mode` has a list of its `extensions dependencies` which are the -the `extension` name and version number. In addition, to use a module element you can use the -`${extensionId}.${moduleType}.${element.name}` schema. For instance, if a mode -requires the left panel with name of `AIPanel` that is added by the -`myAIExtension` via the following `getPanelModule` code, it should address it as -`myAIExtension.panelModule.AIPanel` inside the mode configuration file. In the -background `OHIF` will handle grabbing the correct panel via `ExtensionManager`. - -```js title="extensions/myAIExtension/getPanelModule.js" -import PanelAI from './PanelAI.js'; - -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { - const wrappedAIPanel = () => { - return ( - - ); - }; - - return [ - { - name: 'AIPanel', - iconName: 'list-bullets', - iconLabel: '', - label: 'AI Panel', - isDisabled: studies => {}, // optional - component: wrappedAIPanel, - }, - ]; -} -``` - -Now, let's look at a simplified code of the `basic viewer` mode which consumes various functionalities -from different extensions. - -```js - -const extensionDependencies = { - '@ohif/extension-default': '^3.0.0', - '@ohif/extension-cornerstone': '^3.0.0', - '@ohif/extension-measurement-tracking': '^3.0.0', -}; - -const id = 'viewer'; -const version = '3.0.0'; - -function modeFactory({ modeConfiguration }) { - return { - id, - // ... - routes: [ - { - // ... - layoutTemplate: ({ location, servicesManager }) => { - return { - id: ohif.layout, - props: { - leftPanels: ['@ohif/extension-measurement-tracking.panelModule.seriesList'], - rightPanels: ['@ohif/extension-measurement-tracking.panelModule.trackedMeasurements'], - viewports: [ - { - namespace: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', - displaySetsToDisplay: ['@ohif/extension-default.sopClassHandlerModule.stack'], - }, - ], - }, - }; - }, - }, - ], - extensions: extensionDependencies, - hangingProtocol: ['@ohif/extension-default.hangingProtocolModule.petCT'], - sopClassHandlers: ['@ohif/extension-default.sopClassHandlerModule.stack'], - // ... - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -} - -export default mode -``` - -### Routes - -routes config is an array of route settings, and the overall look and behavior -of the viewer at the designated route is defined by the `layoutTemplate` and -`init` functions for the route. We will learn more about each of the above -properties inside the [route documentation](./routes.md) - - -### HangingProtocols - -Currently, you can pass your defined hanging protocols inside the -`hangingProtocols` property of the mode's config. This will get registered -inside `HangingProtocolService`. - -### SopClassHandlers - -Mode's configuration also accepts the `sopClassHandler` modules that have been -added by the extensions. This information will get used to initialize `DisplaySetService` with the provided SOPClass modules which -handles creation of the displaySets. - - -### Hotkeys - -`hotkeys` is another property in the configuration of a mode that can be defined -to add the specific hotkeys to the viewer at all routes. - -```js -// default hotkeys -import { utils } from '@ohif/ui'; - -const { hotkeys } = utils; - -const myHotkeys = [ - { - commandName: 'setToolActive', - commandOptions: { toolName: 'Zoom' }, - label: 'Zoom', - keys: ['z'], - isEditable: true, - }, - { - commandName: 'scaleUpViewport', - label: 'Zoom In', - keys: ['+'], - isEditable: true, - }, -] - -function modeFactory() { - return { - id: '', - id: '', - displayName: '', - /* - ... - */ - hotkeys: [..hotkeys.defaults.hotkeyBindings, ...myHotkeys], - } -} - -// exports -``` - -## Registration - -Similar to extension registration, `viewer` will look inside the `pluginConfig.json` to -find the `modes` to register. - - -```js title=platform/viewer/pluginConfig.json -// Simplified version of the `pluginConfig.json` file -{ - "extensions": [ - { - "packageName": "@ohif/extension-cornerstone", - "version": "3.0.0" - }, - // ... - ], - "modes": [ - { - "packageName": "@ohif/mode-longitudinal", - "version": "0.0.1" - } - ] -} -``` - -:::note Important -You SHOULD NOT directly register modes in the `pluginConfig.json` file. -Use the provided `cli` to add/remove/install/uninstall modes. Read more [here](../../development/ohif-cli.md) -::: - -The final registration and import of the modes happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md b/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md deleted file mode 100644 index 08c456d29e1..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Installation ---- - -# Modes: Installation - -OHIF-v3 provides the ability to utilize external modes. - - -You can use ohif `cli` tool to install both local and publicly published -modes on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md deleted file mode 100644 index 32ea77cb352..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Lifecycle Hooks ---- - -# Modes: Lifecycle Hooks - -## Overview - -Currently, there are two hooks that are called for modes: - -- onModeEnter -- onModeExit - -## onModeEnter - -This hook gets run after the defined route has been entered by the mode. This -hook can be used to initialize the data, services and appearance of the viewer -upon the first render. - -For instance, in `longitudinal` mode we are using this hook to initialize the -`ToolBarService` and set the window level/width tool to be active and add -buttons to the toolbar. - -```js -function modeFactory() { - return { - id: '', - version: '', - displayName: '', - onModeEnter: ({ servicesManager, extensionManager }) => { - const { ToolBarService } = servicesManager.services; - - const interaction = { - groupId: 'primary', - itemId: 'WindowLevel', - interactionType: 'tool', - commandOptions: undefined, - }; - - ToolBarService.recordInteraction(interaction); - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - ToolBarService.createButtonSection('primary', [ - 'MeasurementTools', - 'Zoom', - 'WindowLevel', - 'Pan', - 'Capture', - 'Layout', - 'MoreTools', - ]); - }, - /* - ... - */ - }; -} -``` - -## onModeExit - -This hook is called when the viewer navigate away from the route in the url. -This is the place for cleaning up data, and services by unsubscribing to the -events. - -For instance, it can be used to reset the `ToolBarService` which reset the -toggled buttons. - -```js -function modeFactory() { - return { - id: '', - displayName: '', - onModeExit: ({ servicesManager, extensionManager }) => { - // Turn of the toggled states on exit - const { ToolBarService } = servicesManager.services; - ToolBarService.reset(); - }, - /* - ... - */ - }; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md b/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md deleted file mode 100644 index 2204bd77ae7..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Routes ---- - -# Mode: Routes - -## Overview - -Modes are tied to a specific route in the viewer, and multiple modes/routes can -be present within a single application. This makes `routes` config, THE most -important part of the mode configuration. - -## Route - -`@ohif/viewer` **compose** extensions to build applications on different routes -for the platform. - -Below, you can see a simplified version of the `longitudinal` mode and the -`routes` section which has defined one `route`. Each route has three different -configuration: - -- **route path**: defines the route path to access the built application for - that route -- **route init**: hook that runs when application enters the defined route path, - if not defined the default init function will run for the mode. -- **route layout**: defines the layout of the application for the specified - route (panels, viewports) - -```js -function modeFactory() { - return { - id: 'viewer', - version: '3.0.0', - displayName: '', - routes: [ - { - path: 'longitudinal', - /*init: ({ servicesManager, extensionManager }) => { - //defaultViewerRouteInit - },*/ - layoutTemplate: ({ location, servicesManager }) => { - return { - id: ohif.layout, - props: { - leftPanels: [ - '@ohif/extension-measurement-tracking.panelModule.seriesList', - ], - rightPanels: [ - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', - ], - viewports: [ - { - namespace: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', - displaySetsToDisplay: [ - '@ohif/extension-default.sopClassHandlerModule.stack', - ], - }, - { - namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', - displaySetsToDisplay: [ - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', - ], - }, - ], - }, - }; - }, - }, - ], - /* - ... - */ - }; -} -``` - -### Route: path - -Upon initialization the viewer will consume extensions and modes and build up -the route desired, these can then be accessed via the study list, or directly -via url parameters. - -> Note: Currently, only one route is built for each mode, but we will enhance -> route creation to create separate routes based on the `path` config for each -> `route` object. - -There are two types of `routes` that are created by the mode. - -- Routes with dataSourceName `/${mode.id}/${dataSourceName}` -- Routes without dataSourceName `/${mode.id}` - -Therefore, navigating to -`http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` -will run the app with the layout and functionalities of the `viewer` mode using -the `defaultDataSourceName` which is defined in the -[App Config](../../configuration/index.md) - -You can use the same exact mode using a different registered data source (e.g., -`dicomjson`) by navigating to -`http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` - -### Route: init - -The mode also has an init hook, which initializes the mode. If you don't define -an `init` function the `default init` function will get run (logic is located -inside `Mode.jsx`). However, you can define you own init function following -certain steps which we will discuss next. - -#### Default init - -Default init function will: - -- `retriveSeriesMetaData` for the `studyInstanceUIDs` that are defined in the - URL. -- Subscribe to `instanceAdded` event, to make display sets after a series have - finished retrieving its instances' metadata. -- Subscribe to `seriesAdded` event, to run the `HangingProtocolService` on the - retrieves series from the study. - -A _simplified_ "pseudocode" for the `defaultRouteInit` is: - -```jsx -async function defaultRouteInit({ - servicesManager, - studyInstanceUIDs, - dataSource, -}) { - const { - DisplaySetService, - HangingProtocolService, - } = servicesManager.services; - - // subscribe to run the function after the event happens - DicomMetadataStore.subscribe( - 'instancesAdded', - ({ StudyInstanceUID, SeriesInstanceUID }) => { - const seriesMetadata = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - DisplaySetService.makeDisplaySets(seriesMetadata.instances); - } - ); - - studyInstanceUIDs.forEach(StudyInstanceUID => { - dataSource.retrieve.series.metadata({ StudyInstanceUID }); - }); - - DicomMetadataStore.subscribe('seriesAdded', ({ StudyInstanceUID }) => { - const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); - HangingProtocolService.run(studyMetadata); - }); - - return unsubscriptions; -} -``` - -#### Writing a custom init - -You can add your custom init function to enhance the default initialization for: - -- Fetching annotations from a server for the current study -- Changing the initial image index of the series to be displayed at first -- Caching the next study in the work list -- Adding a custom sort for the series to be displayed on the study browser panel - -and lots of other modifications. - -You just need to make sure, the mode `dataSource.retrieve.series.metadata`, -`makeDisplaySets` and `run` the HangingProtocols at some point. There are -various `events` that you can subscribe to and add your custom logic. **point to -events** - -For instance for jumping to the slice where a measurement is located at the -initial render, you need to follow a pattern similar to the following: - -```jsx -init: async ({ - servicesManager, - extensionManager, - hotkeysManager, - dataSource, - studyInstanceUIDs, -}) => { - const { DisplaySetService } = servicesManager.services; - - /** - ... - **/ - - const onDisplaySetsAdded = ({ displaySetsAdded, options }) => { - const displaySet = displaySetsAdded[0]; - const { SeriesInstanceUID } = displaySet; - - const toolData = myServer.fetchMeasurements(SeriesInstanceUID); - - if (!toolData.length) { - return; - } - - toolData.forEach(tool => { - const instance = displaySet.images.find( - image => image.SOPInstanceUID === tool.SOPInstanceUID - ); - }); - - MeasurementService.addMeasurement(/**...**/); - }; - - // subscription to the DISPLAY_SETS_ADDED - const { unsubscribe } = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - onDisplaySetsAdded - ); - - /** - ... - **/ - - return unsubscriptions; -}; -``` - -### Route: layoutTemplate - -`layoutTemplate` is the last configuration for a certain route in a `mode`. -`layoutTemplate` is a function that returns an object that configures the -overall layout of the application. The returned object has two properties: - -- `id`: the id of the `layoutTemplate` being used (it should have been - registered via an extension) -- `props`: the required properties to be passed to the `layoutTemplate`. - -For instance `default extension` provides a layoutTemplate that builds the app -using left/right panels and viewports. Therefore, the `props` include -`leftPanels`, `rightPanels` and `viewports` sections. Note that the -`layoutTemplate` defines the properties it is expecting. So, if you write a -`layoutTemplate-2` that accepts a footer section, its logic should be written in -the extension, and any mode that is interested in using `layoutTemplate-2` -**should** provide the `id` for the footer component. - -**What module should the footer be registered?** - -```js -/* -... -*/ -layoutTemplate: ({ location, servicesManager }) => { - return { - id: '@ohif/extension-default.layoutTemplateModule.viewerLayout', - props: { - leftPanels: [ - 'myExtension.panelModule.leftPanel1', - 'myExtension.panelModule.leftPanel2', - ], - rightPanels: ['myExtension.panelModule.rightPanel'], - viewports: [ - { - namespace: 'myExtension.viewportModule.viewport1', - displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop1'], - }, - { - namespace: 'myExtension.viewportModule.viewport2', - displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop2'], - }, - ], - }, - }; -}; -/* -... -*/ -``` - -## FAQ - -> What is the difference between `onModeEnter` and `route.init` - -`onModeEnter` gets run first than `route.init`; however, each route can have -their own `init`, but they share the `onModeEnter`. - -> How can I change the `workList` appearance or add a new login page? - -This is where `OHIF-v3` shines! Since the default `layoutTemplate` is written -for the viewer part, you can simply add a new `layoutTemplate` and use the -component you have written for that route. `Mode` handle showing the correct -component for the specified route. - -```js -function modeFactory() { - return { - id: 'viewer', - displayName: '', - routes: [ - { - path: 'worklist', - init, - layoutTemplate: ({ location, servicesManager }) => { - return { - id: 'worklistLayout', - props: { - component: 'myNewWorkList', - }, - }; - }, - }, - ], - /* - ... - */ - }; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md b/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md deleted file mode 100644 index e3f70af7668..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Validity ---- -# Mode: Validity - - -## Overview -There are two mechanism for checking the validity of a mode for a study. - -- `isValidMode`: which is called on a selected study in the workList. -- `validTags` - - - -## isValidMode -This hook can be used to define a function that return a `boolean` which decided the -validity of the mode based on `StudyInstanceUID` and `modalities` that are in the study. - -For instance, for pet-ct mode, both `PT` and 'CT' modalities should be available inside the study. - -```js -function modeFactory() { - return { - id: '', - displayName: '', - isValidMode: ({ modalities, StudyInstanceUID }) => { - const modalities_list = modalities.split('\\'); - const validMode = ['CT', 'PT'].every(modality => modalities_list.includes(modality)); - return validMode; - }, - /* - ... - */ - } -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md deleted file mode 100644 index bdc411e6cb1..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -sidebar_position: 3 ---- - -# PWA vs Packaged - -It's important to know that the OHIF Viewer project provides two different build -processes: - -```bash -# Static Asset output: For deploying PWAs -yarn run build -``` - -## Progressive Web Application (PWA) - -> [Progressive Web Apps][pwa] are a new breed of web applications that meet the -> [following requirements][pwa-checklist]. Notably, targeting a PWA allows us -> provide a reliable, fast, and engaging experience across different devices and -> network conditions. - -The OHIF Viewer is maintained as a [monorepo][monorepo]. We use WebPack to build -the many small static assets that comprise our application. Also generated is an -`index.html` that will serve as an entry point for loading configuration and the -application, as well as a `service-worker` that can intelligently cache files so -that subsequent requests are from the local file system instead of over the -network. - -You can read more about this particular strategy in our -[Build for Production Deployment Guide](./../deployment/build-for-production.md) - -## Commonjs Bundle (Packaged Script) - -We are not supporting `Commonjs` bundling inside `OHIF-v3`. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json deleted file mode 100644 index 8b57449b24d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Services", - "position": 12 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md deleted file mode 100644 index 927b8b8e541..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: DICOM Metadata Store ---- -# DICOM Metadata Store - - -## Overview -`DicomMetaDataStore` is the central location that stores the metadata in `OHIF-v3`. There -are several APIs to add study/series/instance metadata and also for getting from the store. -DataSource utilize the `DicomMetaDataStore` to add the retrieved metadata to `OHIF Viewer`. - -> In `OHIF-v3` we have significantly changed the architecture of the metadata storage to -> provide a much cleaner way of handling metadata-related tasks and services. Classes such as -> `OHIFInstanceMetadata`, `OHIFSeriesMetadata` and `OHIFStudyMetadata` has been removed and -> replaced with `DicomMetaDataStore`. -> - - -## Events -There are two events that get publish in `DicomMetaDataStore`: - - -| Event | Description | -|-----------------|------------------------------------------------------------------------------------------------| -| SERIES_ADDED | Fires when all series of one study have added their summary metadata to the `DicomMetaDataStore` | -| INSTANCES_ADDED | Fires when all instances of one series have added their metadata to the `DicomMetaDataStore` | - - -## API -Let's find out about the public API for `DicomMetaDataStore` service. - -- `EVENTS`: Object including the events mentioned above. You can subscribe to these events - by calling DicomMetaDataStore.subscribe(EVENTS.SERIES_ADDED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) - -- `addInstances(instances, madeInClient = false)`: adds the instances' metadata to the store. madeInClient is explained in detail below. After - adding to the store it fires the EVENTS.INSTANCES_ADDED. - -- `addSeriesMetadata(seriesSummaryMetadata, madeInClient = false)`: adds series summary metadata. After adding it fires EVENTS.SERIES_ADDED - -- `addStudy(study)`: creates and add study-level metadata to the store. - -- `getStudy(StudyInstanceUID)`: returns the study metadata from the store. It includes all the series and instance metadata in the requested study - -- `getSeries(StudyInstanceUID, SeriesInstanceUID`: returns the series-level metadata for the requested study and series UIDs. - -- `getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID)`: returns the instance metadata based on the study, series and sop instanceUIDs. - -- `geteInstanceFromImageId`: returns the instance metadata based on the requested imageId. It searches the store for the instance that has the same imageId. - - - -### madeInClient - -Since upon adding the metadata to the store, the relevant events are fired, and there are -other services that are subscribed to these events (`HangingProtocolService` or `DisplaySetService`), sometimes -we want to add instance metadata but don't want the events to get fired. For instance, when -you are caching the data for the next study in advance, you probably don't want to trigger hanging protocol -logic, so you set `madeInClient=true` to not fire events. - - -## Storage -As discussed before, there are several API that enables getting metadata from the store and adding to the store. -However, it is good to have an understanding of where they get -stored and in what format and hierarchy. `_model` is a private variable in the store -which holds all the metadata for all studies, series, and instances, and it looks like: - - -```js title="platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js" -const _model = { - studies: [ - { - /** Study Metadata **/ - seriesLists: [ - { - // Series in study from dicom web server 1 (or different backend 1) - series: [ - { - // Series 1 Metadata - instances: [ - { - // Instance 1 metadata of Series 1 - }, - { - // Instance 2 metadata of Series 1 - }, - /** Other instances metadata **/ - ], - }, - { - // Series 2 Metadata - instances: [ - { - // Instance 1 metadata of Series 2 - }, - { - // Instance 2 metadata of Series 1 - }, - /** Other instances metadata **/ - ], - }, - ], - }, - { - // Series in study from dicom web server 2 (or different backend 2) - /** ... **/ - }, - ], - }, - ], -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md deleted file mode 100644 index defc022c610..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: DisplaySet Service ---- -# DisplaySet Service - - -## Overview -`DisplaySetService` handles converting the `instanceMetadata` into `DisplaySet` that `OHIF` uses for the visualization. `DisplaySetService` gets initialized in the `Mode.jsx`. During the initialization `SOPClassHandlerIds` of the `modes` gets registered with the `DisplaySetService`. - -> Based on the instanceMetadata's `SOPClassHandlerId`, the correct module from the registered extensions is found by `OHIF` and its `getDisplaySetsFromSeries` runs to create a DisplaySet for the Series. - - -```js title="platform/core/src/services/DisplaySetService/DisplaySetService.js" -init(extensionManager, SOPClassHandlerIds) { - this.extensionManager = extensionManager; - this.SOPClassHandlerIds = SOPClassHandlerIds; - this.activeDisplaySets = []; -} -``` - -in `Mode.jsx` - -```js title="platform/viewer/src/routes/Mode/Mode.jsx" -export default function ModeRoute(/** ... **/) { - /** ... **/ - const { DisplaySetService } = servicesManager.services - const { sopClassHandlers } = mode - /** ... **/ - useEffect( - () => { - /** ... **/ - - // Add SOPClassHandlers to a new SOPClassManager. - DisplaySetService.init(extensionManager, sopClassHandlers) - - /** ... **/ - } - /** ... **/ - ) - /** ... **/ - return <> /** ... **/ -} -``` - - - - -## Events -There are three events that get broadcasted in `DisplaySetService`: - - - -| Event | Description | -| -------------------- | ---------------------------------------------------- | -| DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache | -| DISPLAY_SETS_CHANGED | Fires when a displayset is changed | -| DISPLAY_SETS_REMOVED | Fires when a displayset is removed | - - - -## API -Let's find out about the public API for `DisplaySetService`. - -- `EVENTS`: Object including the events mentioned above. You can subscribe to these events - by calling DisplaySetService.subscribe(EVENTS.DISPLAY_SETS_CHANGED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) - -- `makeDisplaySets(input, { batch, madeInClient, settings } = {}`): Creates displaySet for the provided - array of instances metadata. Each display set gets a random UID assigned. - - - `input`: Array of instances Metadata - - `batch = false`: If you need to pass array of arrays of instances metadata to have a batch creation - - `madeInClient = false`: Disables the events firing - - `settings = {}`: Hanging protocol viewport or rendering settings. For instance, setting the initial `voi`, or activating a tool upon - displaySet rendering. [Read more about hanging protocols settings here](./HangingProtocolService.md#Settings) - - -- `getDisplaySetByUID`: Returns the displaySet based on the DisplaySetUID. - -- `getDisplaySetForSOPInstanceUID`: Returns the displaySet that includes an image with the provided SOPInstanceUID - -- `getActiveDisplaySets`: Returns the active displaySets - -- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache - -- `holdChangeEvents`: Prevents firing change events (currently only works on add event). - -- `fireHoldChangeEvents`: Causes the change event to be fired IF there were any changes. No longer holds events. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md deleted file mode 100644 index 006a5571218..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md +++ /dev/null @@ -1,348 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Hanging Protocol Service ---- - -# Hanging Protocol Service - -## Overview - -`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol -engine. This service handles the arrangement of the images in the viewport. In -short, the registered protocols will get matched with the DisplaySets that are -available for the study. Each protocol gets a score, and they are ranked. The -winning protocol gets applied and its settings run for the viewports. - -You can read more about hanging protocols -[here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). -In short with `OHIF-v3` hanging protocols you can: - -- Define what layout of the viewport should the viewer starts with (eg 2x2 layout) -- Define which series gets displayed in which position of the layout -- Apply certain initial viewport settings; e.g., inverting the contrast -- Enable certain tools based on what series are displayed: link prostate T2 and - ADC MRI. -- Apply synchronization settings between different viewports or between setting and viewports -- Register custom synchronization settings for viewports -- Register custom attribute extractors -- Select "next display set" from the matching display sets, both on navigation and initial view - -## Skeleton of A Hanging Protocol - -You can find the skeleton of the hanging protocols here: - -```js -const defaultProtocol = { - id: 'test', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - toolGroupIds: [ - 'ctToolGroup', - 'ptToolGroup', - ], - imageLoadStrategy: 'interleaveTopToBottom', // "default" , "interleaveTopToBottom", "interleaveCenter" - protocolMatchingRules: [ - { - id: 'wauZK2QNEfDPwcAQo', - weight: 1, - attribute: 'StudyDescription', - constraint: { - contains: { - value: 'PETCT', - }, - }, - required: false, - }, - ], - stages: [ - { - id: 'hYbmMy3b7pz7GLiaT', - name: 'default', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - displaySets: [ - { - id: 'displaySet', - seriesMatchingRules: [ - { - id: 'GPEYqFLv2dwzCM322', - weight: 1, - attribute: 'Modality', - constraint: { - equals: 'CT', - }, - required: true, - }, - { - id: 'vSjk7NCYjtdS3XZAw', - weight: 1, - attribute: 'numImageFrames', - constraint: { - greaterThan: 10, - }, - }, - ], - studyMatchingRules: [], - }, - ], - viewports: [ - { - viewportOptions: { - viewportId: 'ctAXIAL', - viewportType: 'volume', - orientation: 'axial', - toolGroupId: 'ctToolGroup', - initialImageOptions: { - // index: 5, - preset: 'first', // 'first', 'last', 'middle' - }, - syncGroups: [ - { - type: 'cameraPosition', - id: 'axialSync', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'displaySet', - }, - ], - }, - ], - }, - ], - numberOfPriorsReferenced: -1, -} -``` - -Let's discuss each property in depth. - -- `id`: unique identifier for the protocol -- `name`: Name displayed to the user to select this protocol - -- `protocolMatchingRules`: A list of criteria for the protocol along with the - provided points for ranking. - - - `weight`: weight for the matching rule. Eventually, all the registered - protocols get sorted based on the weights, and the winning protocol gets - applied to the viewer. - - `attribute`: tag that needs to be matched against. This can be either - Study-level metadata or a custom attribute. - [Learn more about custom attribute matching](#custom-attribute) - - - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be - [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] - - A sample of the matching rule is: - - ```js - { - weight: 1, - attribute: 'StudyInstanceUID', - constraint: { - equals: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11', - }, - required: true, - } - ``` - - -- `stages`: Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. - Therefore, the `stages` property is array of objects, each object being one stage. - - - `displaySets`: Defines the matching rules for which display sets to use. - - `viewportStructure`: Defines the layout of the viewer. You can define the - number of `rows` and `columns`. - - `viewports` defines the actual viewports to display. There should be `rows * columns` number of - these `viewports` property, ordered rows first, then columns. - - - ```js - stages: [ - { - id: 'hYbmMy3b7pz7GLiaT', - name: 'oneByTwo', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 3, - }, - }, - viewports: [ - // viewport 1 - { - viewportOptions: { - viewportId: 'ctAXIAL', - viewportType: 'volume', - orientation: 'axial', - toolGroupId: 'ctToolGroup', - initialImageOptions: { - // index: 5, - preset: 'first', // 'first', 'last', 'middle' - }, - syncGroups: [ - { - type: 'cameraPosition', - id: 'axialSync', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'displaySet', - }, - ], - }, - ]; - ``` - -## Events - -There are two events that get publish in `HangingProtocolService`: - -| Event | Description | -| ------------ | -------------------------------------------------------------------- | -| NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` | -| STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols | - -## API - -- `getState`: returns an array: `[matchDetails, hpAlreadyApplied]`: - - - `matchDetails`: matching details for the series - - `hpAlreadyApplied`: An array which tracks whether HPServices has been - applied on each viewport. - -- `addProtocols`: adds provided protocols to the list of registered protocols - for matching - -- `run({ studies, displaySets }, protocol)`: runs the HPService with the provided - list of studies, display sets and optional protocol. - If protocol is not given, HP Matching - engine will search all the registered protocols for the best matching one - based on the constraints. - -- `addCustomAttribute`: adding a custom attribute for matching. (see below) - -Default initialization of the modes handles running the `HangingProtocolService` - -## Custom Attribute -In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance, -if you have assigned a `timepointId` to each study, and you want to match based on it. -Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching. - -In some situations, you might want to match based on a custom attribute and not -the DICOM tags. For instance, if you have assigned a `timepointId` to each study -and you want to match based on it. Good news is that, in `OHIF-v3` you can -define you custom attribute and use it for matching. - -There are various ways that you can let `HangingProtocolService` know of you -custom attribute. We will show how to add it inside the an extension. This extension -also shows how to register a sync group service which can be referenced -in the sync group settings. - -```js -const myCustomProtocol = { - id: 'myCustomProtocol', - /** ... **/ - protocolMatchingRules: [ - { - id: 'vSjk7NCYjtdS3XZAw', - attribute: 'timepointId', - constraint: { - equals: 'first', - }, - required: false, - }, - ], -... - -// Custom function for custom attribute -const getTimePointUID = metaData => { - // requesting the timePoint Id - return myBackEndAPI(metaData); -}; - - preRegistration: ({ - servicesManager, - }) => { - const { HangingProtocolService, SyncGroupService } = servicesManager.services; - HangingProtocolService.addCustomAttribute('timepointId', 'TimePoint ID', getTimePointUID); - SyncGroupService.setSynchronizer('initialzoompan', initialZoomPan); - } -``` - -## Viewport Settings - -You can define custom settings to be applied to each viewport. There are two -types of settings: - -- `viewport settings`: Currently we support two viewport settings - - - `voi`: applying an initial `voi` by setting the windowWidth and windowCenter - - `inverted`: inverting the viewport color (e.g., for PET images) - -- `props settings`: Running commands after the first render; e.g., enabling the - link tool - -Examples of each settings are : - -```js -viewportSettings: [ - { - options: { - itemId: 'SyncScroll', - interactionType: 'toggle', - commandName: 'toggleSynchronizer', - commandOptions: { toggledState: true }, - }, - commandName: 'setToolActive', - type: 'props', - }, -]; -``` - -and - -```js -viewportSettings: [ - { - options: { - voi: { - windowWidth: 400, - windowCenter: 40, - }, - }, - commandName: '', - type: 'viewport', - }, -]; -``` - -## Sync Groups -The sync groups are listeners to events that synchronize viewport settings to -some other settings. There are three default/provided sync groups: `zoomPan`, -`cameraPosition` and `voi`. These are defined in the `syncGroups` array. -Additionally, other synchronization types can be created and registered on the -`SyncGroupService.setSynchronizer`, by registering a new id, and a creator method. - -The sync group service is specific to the `cornerstone-extension` because the -actual behaviour of the synchronizers is dependent on the specific viewport. -Different viewport types could redifine the same synchronizer names in -different ways appropriate to that viewport. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md deleted file mode 100644 index 74605bce1c5..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Measurement Service ---- - -# Measurement Service - -## Overview - -`MeasurementService` handles the internal measurement representation inside -`OHIF` platform. Developers can add their custom `sources` with `mappers` to -enable adding measurements inside OHIF. Currently, we are maintaining -`CornerstoneTools` annotations and corresponding mappers can be found inside the -`cornerstone` extension. However, `MeasurementService` can be configured to work -with any custom tools given that its `mappers` is added to the -`MeasurementService`. We can see the overall architecture of the -`MeasurementService` below: - -![services-measurements](../../../assets/img/services-measurements.png) - -## Events - -There are seven events that get publish in `MeasurementService`: - -| Event | Description | -| --------------------- | ------------------------------------------------------ | -| MEASUREMENT_UPDATED | Fires when a measurement is updated | -| MEASUREMENT_ADDED | Fires when a new measurement is added | -| RAW_MEASUREMENT_ADDED | Fires when a raw measurement is added (e.g., dicom-sr) | -| MEASUREMENT_REMOVED | Fires when a measurement is removed | -| MEASUREMENTS_CLEARED | Fires when all measurements are deleted | -| JUMP_TO_MEASUREMENT | Fires when a measurement is requested to be jump to | - -## API - -- `getMeasurements`: returns array of measurements - -- `getMeasurement(id)`: returns the corresponding measurement based on the - provided Id. - -- `remove(id, source)`: removes a measurement and broadcasts the - `MEASUREMENT_REMOVED` event. - -- `clearMeasurements`: removes all measurements and broadcasts - `MEASUREMENTS_CLEARED` event. - -- `createSource(name, version)`: creates a new measurement source, generates a - uid and adds it to the `sources` property of the service. - -- `addMapping(source, definition, matchingCriteria, toSourceSchema, toMeasurementSchema)`: - adds a new measurement matching criteria along with mapping functions. We will - learn more about [source/mappers below](#source--mappers) - -- `update`: updates the measurement details and fires `MEASUREMENT_UPDATED` - -- `addRawMeasurement(source,definition,data,toMeasurementSchema,dataSource = {}` - : adds a raw measurement into a source so that it may be converted to/from - annotation in the same way. E.g. import serialized data of the same form as - the measurement source. Fires `MEASUREMENT_UPDATED` or `MEASUREMENT_ADDED`. - Note that, `MeasurementService` handles finding the correct mapper upon new - measurements; however, `addRawMeasurement` provides more flexibility. You can - take a look into its usage in `dicom-sr` extension. - - - `source`: The measurement source instance. - - `definition`: The source definition you want to add the measurement to. - - `data`: The data you wish to add to the source. - - `toMeasurementSchema`: A function to get the `data` into the same shape as - the source definition. - -- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have - subscribed to `JUMP_TO_MEASUREMENT`. - -## Source / Mappers - -To create a custom measurement source and relevant mappers for each tool, you -can take a look at the `init.js` inside the `cornerstone` extension. In which we -are registering our `CornerstoneTools-v4` measurement source to -MeasurementService. Let's take a peek at the _simplified_ implementation -together. To achieve this, for each tool, we need to provide three mappers: - -- `matchingCriteria`: criteria used for finding the correct mapper for the drawn - tool. -- `toAnnotation`: tbd -- `toMeasurement`: a function that converts the tool data to OHIF internal - representation of measurement data. - -```js title="extensions/cornerstone/src/utils/measurementServiceMappings/Length.js" -function toMeasurement( - csToolsAnnotation, - DisplaySetService, - getValueTypeFromToolType -) { - const { element, measurementData } = csToolsAnnotation; - - /** ... **/ - - const { - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes(element); - - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - - /** ... **/ - return { - id: measurementData.id, - SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.label, - description: measurementData.description, - unit: measurementData.unit, - length: measurementData.length, - type: getValueTypeFromToolType(tool), - points: getPointsFromHandles(measurementData.handles), - }; -} - -////////////////////////////////////////// - -// extensions/cornerstone/src/init.js - -const Length = { - toAnnotation, - toMeasurement, - matchingCriteria: [ - { - valueType: MeasurementService.VALUE_TYPES.POLYLINE, - points: 2, - }, - ], -}; - -const _initMeasurementService = (MeasurementService, DisplaySetService) => { - /** ... **/ - - const csToolsVer4MeasurementSource = MeasurementService.createSource( - 'CornerstoneTools', - '4' - ); - - /* Mappings */ - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'Length', - Length.matchingCriteria, - toAnnotation, - toMeasurement - ); - - /** Other tools **/ - return csToolsVer4MeasurementSource; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md deleted file mode 100644 index 92ab50048b2..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md +++ /dev/null @@ -1,226 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Toolbar Service ---- - -# Toolbar Service - -## Overview - -`ToolBarService` handles the toolbar section buttons, and what happens when a -button is clicked by the user. - -
- -
- -## Events - -| Event | Description | -| ----------------------- | ---------------------------------------------------------------------- | -| TOOL_BAR_MODIFIED | Fires when a button is added/removed to the toolbar | -| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolBarService state is modified | - -## API - -- `recordInteraction(interaction)`: executes the provided interaction which is - an object providing the following properties to the ToolBarService: - - - `interactionType`: can be `tool`, `toggle` and `action`. We will discuss - more each type below. - - `itemId`: tool name - - `groupId`: the Id for the tool button group; e.g., `Wwwc` which holds - presets. - - `commandName`: if tool has a command attached to run - - `commandOptions`: arguments for the command. - -- `reset`: reset the state of the toolbarService, set the primary tool to be - `Wwwc` and unsubscribe tools that have registered their functions. - -- `addButtons`: add the button definition to the service. - [See below for button definition](#button-definitions). - -- `setButtons`: sets the buttons defined in the service. It overrides all the - previous buttons - -- `getActiveTools`: returns the active tool + all the toggled-on tools - -## State - -ToolBarService has an internal state that gets updated per tool interaction and -tracks the active toolId, state of the buttons that have toggled state, and the -group buttons and which tool in each group is active. - -```js -state = { - primaryToolId: 'Wwwc', - toggles: { - /* id: true/false */ - }, - groups: { - /* track most recent click per group...*/ - }, -}; -``` - -## Interaction type - -There are three main types that a tool can have which is defined in the -interaction object. - -- `tool`: setting a tool to be active; e.g., measurement tools -- `toggle`: toggling state of a tool; e.g., viewport link (sync) -- `action`: performs a registered action outside of the ToolBarService; e.g., - capture - -A _simplified_ implementation of the ToolBarService is: - -```js -export default class ToolBarService { - /** ... **/ - recordInteraction(interaction) { - /** ... **/ - switch (interactionType) { - case 'action': { - break; - } - case 'tool': { - this.state.primaryToolId = itemId; - - commandsManager.runCommand('setToolActive', interaction.commandOptions); - break; - } - case 'toggle': { - this.state.toggles[itemId] = - this.state.toggles[itemId] === undefined - ? true - : !this.state.toggles[itemId]; - interaction.commandOptions.toggledState = this.state.toggles[itemId]; - break; - } - default: - throw new Error(`Invalid interaction type: ${interactionType}`); - } - /** ... **/ - } - /** ... **/ -} -``` - -## Button Definitions - -The simplest toolbarButtons definition has the following properties: - -![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) - -```js -{ - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, -``` - -| property | description | values | -| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | -| `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | -| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | - -There are three main types of toolbar buttons: - -- `tool`: buttons that enable a tool by running the `setToolActive` command with - the `commandOptions` -- `toggle`: buttons that acts as a toggle: e.g., linking viewports -- `action`: buttons that executes an action: e.g., capture button to save - screenshot - -## Nested Buttons - -You can use the `ohif.splitButton` type to build a button with extra tools in -the dropdown. - -- First you need to give your `primary` tool definition to the split button -- the `secondary` properties can be a simple arrow down (`chevron-down` icon) -- For adding the extra tools add them to the `items` list. - -You can see below how `longitudinal` mode is using the available toolbarModule -to create `MeasurementTools` nested button - -![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) - -```js title="modes/longitudinal/src/toolbarButtons.js" -{ - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', - }, - items: [ - // Length tool - { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - // Bidirectional tool - { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } - }, - // Ellipse tool - { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } - }, - // Circle tool - { - id: 'CircleROI', - icon: 'tool-circle', - label: 'Circle', - type: 'tool', - commandOptions: { - toolName: 'CircleROI', - } - }, - ], - }, -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json deleted file mode 100644 index 984ac9a4208..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Data Services", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md deleted file mode 100644 index fa9128ae4da..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - -Data services are the first category of services which deal with handling non-ui -related state Each service have their own internal state which they handle. - -> We have replaced the _redux_ store. Instead, we have introduced various -> services and a pub/sub pattern to subscribe and run, which makes the `OHIF-v3` -> architecture nice and clean. - -We maintain the following non-ui Services: - -- [DicomMetadata Store](./../data/DicomMetadataStore.md) -- [DisplaySet Service](./../data/DisplaySetService.md) -- [Hanging Protocol Service](../data/HangingProtocolService.md) -- [Toolbar Service](../data/ToolBarService.md) -- [Measurement Service](../data/MeasurementService.md) - -## Service Architecture - -![services-data](../../../assets/img/services-data.png) - -> We have explained services and how to create a custom service in the -> [`ServiceManager`](../../managers/service.md) section of the docs - -To recap: The simplest service return a new object that has a `name` property, -and `Create` method which instantiate the service class. The "Factory Function" -that creates the service is provided with the implementation (this is slightly -different for UI Services). - -```js -// extensions/customExtension/src/services/backEndService/index.js -import backEndService from './backEndService'; - -export default function WrappedBackEndService(serviceManager) { - return { - name: 'myService', - create: ({ configuration = {} }) => { - return new backEndService(serviceManager); - }, - }; -} -``` - -A service, once created, can be registered with the `ServicesManager` to make it -accessible to extensions. Similarly, the application code can access named -services from the `ServicesManager`. - -[Read more of how to design a new custom service and register it](../../managers/service.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/index.md deleted file mode 100644 index 87d88e7ad2b..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/index.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Services - -## Overview - -Services are "concern-specific" code modules that can be consumed across layers. -Services provide a set of operations, often tied to some shared state, and are -made available to through out the app via the `ServicesManager`. Services are -particularly well suited to address [cross-cutting -concerns][cross-cutting-concerns]. - -Each service should be: - -- self-contained -- able to fail and/or be removed without breaking the application -- completely interchangeable with another module implementing the same interface - -> In `OHIF-v3` we have added multiple non-UI services and have introduced -> **pub/sub** pattern to reduce coupling between layers. -> -> [Read more about Pub/Sub](./pubsub.md) - -## Services - -The following services is available in the `OHIF-v3`. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ServiceTypeDescription
- - DicomMetadataStore (NEW) - - Data Service - DicomMetadataStore -
- - DisplaySetService (NEW) - - Data Service - DisplaySetService -
- - HangingProtocolService (NEW) - - Data Service - HangingProtocolService -
- - MeasurementService (MODIFIED) - - Data Service - MeasurementService -
- - ToolBarService (NEW) - - Data Service - ToolBarService -
- - ViewportGridService (NEW) - - UI Service - ViewportGridService -
- - Cine Service (NEW) - - UI Service - cine -
- - UIDialogService - - UI Service - UIDialogService -
- - UIModalService - - UI Service - UIModalService -
- - UINotificationService - - UI Service - UINotificationService -
- - UIViewportDialogService (NEW) - - UI Service - UIViewportDialogService -
- - - - - -[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services -[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js -[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md b/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md deleted file mode 100644 index fae6978e6a8..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Pub Sub ---- - -# Pub sub - -## Overview - -Publish–subscribe pattern is a messaging pattern that is one of the fundamentals -patterns used in reusable software components. - -In short, services that implement this pattern, can have listeners subscribed -to their broadcasted events. After the event is fired, the corresponding -listener will execute the function that is registered. - -You can read more about this design pattern -[here](https://cloud.google.com/pubsub/docs/overview). - -## Example: Default Initialization - -In `Mode.jsx` we have a default initialization that demonstrates a series of -subscriptions to various events. - -```js -async function defaultRouteInit({ - servicesManager, - studyInstanceUIDs, - dataSource, -}) { - const { - DisplaySetService, - HangingProtocolService, - } = servicesManager.services; - - const unsubscriptions = []; - - const { - unsubscribe: instanceAddedUnsubscribe, - } = DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.INSTANCES_ADDED, - ({ StudyInstanceUID, SeriesInstanceUID, madeInClient = false }) => { - const seriesMetadata = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - - DisplaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient); - } - ); - - unsubscriptions.push(instanceAddedUnsubscribe); - - studyInstanceUIDs.forEach(StudyInstanceUID => { - dataSource.retrieve.series.metadata({ StudyInstanceUID }); - }); - - const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.SERIES_ADDED, - ({ StudyInstanceUID }) => { - const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); - HangingProtocolService.run(studyMetadata, DisplaySetService.getActiveDisplaySets()); - } - ); - unsubscriptions.push(seriesAddedUnsubscribe); - - return unsubscriptions; -} -``` - -## Unsubscription - -You need to be careful if you are adding custom subscriptions to the app. Each -subscription will return an unsubscription function that needs to be executed on -component destruction to avoid adding multiple subscriptions to the same -observer. - -Below, we can see `simplified` `Mode.jsx` and the corresponding `useEffect` -where the unsubscription functions are executed upon destruction. - -```js title="platform/viewer/src/routes/Mode/Mode.jsx" -export default function ModeRoute(/**..**/) { - /**...**/ - useEffect(() => { - /**...**/ - - DisplaySetService.init(extensionManager, sopClassHandlers); - - extensionManager.onModeEnter(); - mode?.onModeEnter({ servicesManager, extensionManager }); - - hangingProtocols.forEach(extentionProtocols => { - const { protocols } = extensionManager.getModuleEntry(extentionProtocols); - HangingProtocolService.addProtocols(protocols); - }); - - const setupRouteInit = async () => { - if (route.init) { - return await route.init(/**...**/); - } - - return await defaultRouteInit(/**...**/); - }; - - let unsubscriptions; - setupRouteInit().then(unsubs => { - unsubscriptions = unsubs; - }); - - return () => { - extensionManager.onModeExit(); - mode?.onModeExit({ servicesManager, extensionManager }); - unsubscriptions.forEach(unsub => { - unsub(); - }); - }; - }); - return <> /**...**/ ; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json deleted file mode 100644 index 9c012134eb0..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "UI Services", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md deleted file mode 100644 index 707b5d97f7f..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: CINE Service ---- - -# CINE Service - -TODO... diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md deleted file mode 100644 index 5ba4a539057..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - - - -A typical web application will have components and state for common UI like -modals, notifications, dialogs, etc. A UI service makes it possible to leverage -these components from an extension. - -We maintain the following UI Services: - -- [UI Notification Service](ui-notification-service.md) -- [UI Modal Service](ui-modal-service.md) -- [UI Dialog Service](ui-dialog-service.md) -- [UI Viewport Dialog Service](ui-viewport-dialog-service.md) -- [CINE Service](cine-service.md) -- [Viewport Grid Service](viewport-grid-service.md) - - - -![UIService](../../../assets/img/ui-services.png) - - - -## Providers for UI services - -**There are several context providers that wraps the application routes. This -makes the context values exposed in the app, and service's `setImplementation` -can get run to override the implementation of the service.** - -```js title="platform/viewer/src/App.jsx" -function App({ config, defaultExtensions }) { - /**...**/ - /**...**/ - return ( - /**...**/ - - - - - - - {appRoutes} - - - - - - - /**...**/ - ); -} -``` - -## Example - -For instance `UIModalService` has the following Public API: - -```js title="platform/core/src/services/UIModalService/index.js" -const publicAPI = { - name, - hide: _hide, - show: _show, - setServiceImplementation, -}; - -function setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, -}) { - /** ... **/ - serviceImplementation._hide = hideImplementation; - serviceImplementation._show = showImplementation; - /** ... **/ -} - -export default { - name: 'UIModalService', - create: ({ configuration = {} }) => { - return publicAPI; - }, -}; -``` - -`UIModalService` implementation can be set (override) in its context provider. -For instance in `ModalProvider` we have: - -```js title="platform/ui/src/contextProviders/ModalProvider.jsx" -import { Modal } from '@ohif/ui'; - -const ModalContext = createContext(null); -const { Provider } = ModalContext; - -export const useModal = () => useContext(ModalContext); - -const ModalProvider = ({ children, modal: Modal, service }) => { - const DEFAULT_OPTIONS = { - content: null, - contentProps: null, - shouldCloseOnEsc: true, - isOpen: true, - closeButton: true, - title: null, - customClassName: '', - }; - - const show = useCallback(props => setOptions({ ...options, ...props }), [ - options, - ]); - - const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [ - DEFAULT_OPTIONS, - ]); - - useEffect(() => { - if (service) { - service.setServiceImplementation({ hide, show }); - } - }, [hide, service, show]); - - const { - content: ModalContent, - contentProps, - isOpen, - title, - customClassName, - shouldCloseOnEsc, - closeButton, - } = options; - - return ( - - {ModalContent && ( - - - - )} - {children} - - ); -}; - -export default ModalProvider; - -export const ModalConsumer = ModalContext.Consumer; -``` - -Therefore, anywhere in the app that we have access to react context we can use -it by calling the `useModal` from `@ohif/ui`. As a matter of fact, we are -utilizing the modal for the preference window which shows the hotkeys after -clicking on the gear button on the right side of the header. - -A `simplified` code for our worklist is: - -```js title="platform/viewer/src/routes/WorkList/WorkList.jsx" -import { useModal, Header } from '@ohif/ui'; - -function WorkList({ - history, - data: studies, - dataTotal: studiesTotal, - isLoadingData, - dataSource, - hotkeysManager, -}) { - const { show, hide } = useModal(); - - /** ... **/ - - const menuOptions = [ - { - title: t('Header:About'), - icon: 'info', - onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' }), - }, - { - title: t('Header:Preferences'), - icon: 'settings', - onClick: () => - show({ - title: t('UserPreferencesModal:User Preferences'), - content: UserPreferences, - contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( - hotkeyDefaults - ), - hotkeyDefinitions, - onCancel: hide, - currentLanguage: currentLanguage(), - availableLanguages, - defaultLanguage, - onSubmit: state => { - i18n.changeLanguage(state.language.value); - hotkeysManager.setHotkeys(state.hotkeyDefinitions); - hide(); - }, - onReset: () => hotkeysManager.restoreDefaultBindings(), - }, - }), - }, - ]; - /** ... **/ - return ( -
- /** ... **/ -
- /** ... **/ -
- ); -} -``` - - - - - - - -## Tips & Tricks - -It's important to remember that all we're doing is making it possible to control -bits of the application's UI from an extension. Here are a few non-obvious -takeaways worth mentioning: - -- Your application code should continue to use React context - (consumers/providers) as it normally would -- You can substitute our "out of the box" UI implementations with your own -- You can create and register your own UI services -- You can choose not to register a service or provide a service implementation -- In extensions, you can provide fallback/alternative behavior if an expected - service is not registered - - No `UIModalService`? Use the `UINotificationService` to notify users. -- You can technically register a service in an extension and expose it to the - core application - -> Note: These are recommended patterns, not hard and fast rules. Following them -> will help reduce confusion and interoperability with the larger OHIF -> community, but they're not silver bullets. Please speak up, create an issue, -> if you would like to discuss new services or improvements to this pattern. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md deleted file mode 100644 index 152841ae625..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: UI Dialog Service ---- -# UI Dialog Service - -Dialogs have similar characteristics to that of Modals, but often with a -streamlined focus. They can be helpful when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| -------------- | ------------------------------------------------------ | -| `create()` | Creates a new Dialog that is displayed until dismissed | -| `dismiss()` | Dismisses the specified dialog | -| `dismissAll()` | Dismisses all dialogs | - -## Implementations - -| Implementation | Consumer | -| ------------------------------------ | -------------------------- | -| [Dialog Provider][dialog-provider]\* | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIDialogService/index.js -[dialog-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/DialogProvider.js -[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md deleted file mode 100644 index cb36d7e6cf6..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: UI Modal Service ---- -# UI Modal Service - -Modals have similar characteristics to that of Dialogs, but are often larger, -and only allow for a single instance to be viewable at once. They also tend to -be centered, and not draggable. They're commonly used when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - -
- -
- -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| ---------- | ------------------------------------- | -| `hide()` | Hides the open modal | -| `show()` | Shows the provided content in a modal | - -## Implementations - -| Implementation | Consumer | -| ---------------------------------- | --------- | -| [Modal Provider][modal-provider]\* | Modal.jsx | - -`*` - Denotes maintained by OHIF - - - - - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIModalService/index.js -[modal-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/ModalProvider.js -[modal-consumer]: https://github.com/OHIF/Viewers/tree/master/platform/ui/src/components/ohifModal -[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md deleted file mode 100644 index 96185943482..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: UI Notification Service ---- -# UI Notification Service - -Notifications can be annoying and disruptive. They can also deliver timely -helpful information, or expedite the user's workflow. Here is some high level -guidance on when and how to use them: - -- Notifications should be non-interfering (timely, relevant, important) -- We should only show small/brief notifications -- Notifications should be contextual to current behavior/actions -- Notifications can serve warnings (acting as a confirmation) - -If you're curious about the DOs and DON'Ts of notifications, check out this -article: ["How To Design Notifications For Better UX"][ux-article] - - - -
- -
- - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| ---------- | --------------------------------------- | -| `hide()` | Hides the specified notification | -| `show()` | Creates and displays a new notification | - -## Implementations - -| Implementation | Consumer | -| ---------------------------------------- | ----------------------------------------- | -| [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* | - -`*` - Denotes maintained by OHIF - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UINotificationService/index.js -[snackbar-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/SnackbarProvider.js -[snackbar-container]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/components/snackbar/SnackbarContainer.js -[ux-article]: https://uxplanet.org/how-to-design-notifications-for-better-ux-6fb0711be54d - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md deleted file mode 100644 index 7370428459d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: UI Viewport Dialog Service ---- - -# UI Viewport Dialog Service - -## Overview -This is a new UI service, that creates a modal inside the viewport. - -Dialogs have similar characteristics to that of Modals, but often with a -streamlined focus. They can be helpful when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - - -
- -
- -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| -------------- | ------------------------------------------------------ | -| `create()` | Creates a new Dialog that is displayed until dismissed | -| `dismiss()` | Dismisses the specified dialog | -| `dismissAll()` | Dismisses all dialogs | - -## Implementations - -| Implementation | Consumer | -| ------------------------ | -------------------------- | -| [ViewportDialogProvider] | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - - -## State - -```js -const DEFAULT_STATE = { - viewportIndex: null, - message: undefined, - type: 'info', // "error" | "warning" | "info" | "success" - actions: undefined, // array of { type, text, value } - onSubmit: () => { - console.log('btn value?'); - }, - onOutsideClick: () => { - console.warn('default: onOutsideClick') - }, - onDismiss: () => { - console.log('dismiss? -1'); - }, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md deleted file mode 100644 index 39e2378a327..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Viewport Grid Service ---- - -# Viewport Grid Service - -## Overview - -This is a new UI service, that handles the grid layout of the viewer. - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| --------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | -| `getState()` | Gets the states of the viewport (see below) | -| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | -| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | -| `reset()` | Resets the default states | -| `getNumViewportPanes()` | Gets the number of visible viewport panes | - -## Implementations - -| Implementation | Consumer | -| ---------------------- | -------------------------- | -| [ViewportGridProvider] | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - -## State - -```js -const DEFAULT_STATE = { - // starting from null, hanging - // protocol will defined number of rows and cols - numRows: null, - numCols: null, - viewports: [ - /* - * { - * displaySetInstanceUID: string, - * } - */ - ], - activeViewportIndex: 0, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/themeing.md b/platform/docs/versioned_docs/version-3.0/platform/themeing.md deleted file mode 100644 index 852c5d3982c..00000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/themeing.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Theming ---- - -# Viewer: Theming - -`OHIF-v3` has introduced the -[`LayoutTemplateModule`](./extensions/modules/layout-template.md) which enables -addition of custom layouts. You can easily design your custom components inside -an extension and consume it via the layoutTemplate module you write. - -## Tailwind CSS - -[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for -creating custom user interfaces. - -Below you can see a compiled version of the tailwind configs. Each section can -be edited accordingly. For instance screen size break points, primary and -secondary colors, etc. - -```js -module.exports = { - prefix: '', - important: false, - separator: ':', - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - }, - colors: { - overlay: 'rgba(0, 0, 0, 0.8)', - transparent: 'transparent', - black: '#000', - white: '#fff', - initial: 'initial', - inherit: 'inherit', - - indigo: { - dark: '#0b1a42', - }, - aqua: { - pale: '#7bb2ce', - }, - - primary: { - light: '#5acce6', - main: '#0944b3', - dark: '#090c29', - active: '#348cfd', - }, - - secondary: { - light: '#3a3f99', - main: '#2b166b', - dark: '#041c4a', - active: '#1f1f27', - }, - - common: { - bright: '#e1e1e1', - light: '#a19fad', - main: '#fff', - dark: '#726f7e', - active: '#2c3074', - }, - - customgreen: { - 100: '#05D97C', - }, - - customblue: { - 100: '#c4fdff', - 200: '#38daff', - }, - }, - }, -}; -``` - -You can also use the color variable like before. For instance: - -```js -primary: { - default: ‘var(--default-color)‘, - light: ‘#5ACCE6’, - main: ‘#0944B3’, - dark: ‘#090C29’, - active: ‘#348CFD’, -} -``` - -## White Labeling - -A white-label product is a product or service produced by one company (the -producer) that other companies (the marketers) rebrand to make it appear as if -they had made it - -[Wikipedia: White-Label Product](https://en.wikipedia.org/wiki/White-label_product) - -Current white-labeling options are limited. We expose the ability to replace the -"Logo" section of the application with a custom "Logo" component. You can do -this by adding a whiteLabeling key to your configuration file. - -```js -window.config = { - /** .. **/ - whiteLabeling: { - createLogoComponentFn: function(React) { - return React.createElement( - 'a', - { - target: '_blank', - rel: 'noopener noreferrer', - className: 'text-white underline', - href: 'http://radicalimaging.com', - }, - React.createElement('h5', {}, 'RADICAL IMAGING') - ); - }, - }, - /** .. **/ -}; -``` - -> You can simply use the stylings from tailwind CSS in the whiteLabeling - -In addition to text, you can also add your custom logo. You can put them -inside the platform/viewer/public/assets folder and use them in the -whiteLabeling section. - -```js -window.config = { - /** .. **/ - whiteLabeling: { - createLogoComponentFn: function(React) { - return React.createElement( - 'a', - { - target: '_self', - rel: 'noopener noreferrer', - className: 'text-purple-600 line-through', - href: '/', - }, - React.createElement('img', { - src: './assets/customLogo.svg', - // className: 'w-8 h-8', - }) - ); - }, - }, - /** .. **/ -}; -``` - -The output will look like - -![custom-logo](../assets/img/custom-logo.png) - - - - -[wikipedia]: https://en.wikipedia.org/wiki/White-label_product - diff --git a/platform/docs/versioned_docs/version-3.0/release-notes.md b/platform/docs/versioned_docs/version-3.0/release-notes.md deleted file mode 100644 index 242221bd7d9..00000000000 --- a/platform/docs/versioned_docs/version-3.0/release-notes.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Release Notes ---- - -# Release Notes - -> New `OHIF-v3` architecture has made OHIF a general purpose extensible medical -> imaging **platform**, as opposed to a configurable viewer. - -## What's new in `OHIF-v3` - -`OHIF-v3` is our second try for a React-based viewer, and is the third version -of our medical image web viewers from the start. The summary of changes includes: - -- Addition of workflow modes - - Often, medical imaging use cases involves lots of specific workflows that - re-use functionalities. We have added the capability of workflow modes, that - enable people to customize user interface and configure application for - specific workflow. - - The idea is to re-use the functionalities that extensions provide and create - a workflow. Brain segmentation workflow is different from prostate - segmentation in UI for sure; however, they share the segmentation tools that - can be re-used. - - Our vision is that technical people focus of developing extensions which - provides core functionalities, and experts to build modes by picking the - appropriate functionalities from each extension. - -* UI has been completely redesigned with modularity and workflow modes in mind. -* New UI components have been built with Tailwind CSS -* Redux store has been removed from the viewer in favour of services backed by - React's Context API - -Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OHIF-v2 functionalitiesOHIF-v3Comment
Rendering of 2D images via Cornerstone
Study List
Series Browser
DICOM JSON
2D Tools via CornerstoneTools
OpenID Connect standard authentication flow for connecting to identity providers
Internationalization
Drag/drop DICOM data into the viewer (see https://viewer.ohif.org/local)
White-labelling: Easily replace the OHIF Logo with your logo
DICOM Whole-slide imaging viewport🔜In Progress
IHE Invoke Image Display - Standard-compliant launching of the viewer (e.g. from PACS or RIS)🔜Not Started
DICOM PDF support🔜Not Started
Displaying non-renderable DICOM as HTML🔜Not Started
Segmentation support🔜Not Started
RT STRUCT support🔜Not Started
DICOM upload to PACS🔜Not Started
Google Cloud adapter🔜Not Started
VTK Extension + MIP / MPR layoutOther plans that involves amazing news soon!
UMD Build (Embedded Viewer). The problem is that this breaks a bunch of extensions that rely on third party scripts (e.g. VTK) which have their own web worker loaders.
diff --git a/platform/docs/versioned_docs/version-3.0/resources.md b/platform/docs/versioned_docs/version-3.0/resources.md deleted file mode 100644 index be7b10ed756..00000000000 --- a/platform/docs/versioned_docs/version-3.0/resources.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -sidebar_position: 9 -sidebar_label: Resources ---- - -# Resources - -Throughout the development of the OHIF Viewer, we have participated in various -conferences and "hackathons". In this page, we will provide the presentations -and other resources that we have provided to the community in the past: - -## 2022 - -### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) - -The Project Week is a week-long hackathon of hands-on activity in which medical -image computing researchers. OHIF team participated and gave a talk on OHIF and -Cornerstone in the 36th Project Week: -[[Slides]](https://docs.google.com/presentation/d/1-GtOKmr2cQi-r3OFyseSmgLeurtB3KXUkGMx2pVLh1I/edit?usp=sharing) -[[Video]](https://vimeo.com/668339696/63a2c48de8) - -## 2021 - -### [NA-MIC Project Week 35th 2021 - Remote](https://github.com/NA-MIC/ProjectWeek/tree/master/PW35_2021_Virtual) - -The Project Week is a week-long hackathon of hands-on activity in which medical -image computing researchers. OHIF team participated in the 35th Project Week -in 2021. -[[Slides]](https://docs.google.com/presentation/d/1KYNjuiI8lT1foQ4P9TGNV0lBhM6H-5KBs0wkYj4JJbk/edit?usp=sharing) - -### Chan Zuckerberg Initiative (CZI) - -Project presentations and demonstrations of Essential Open Source Software for -Science (EOSS) grantees -[[Slides]](https://docs.google.com/presentation/d/1_CLtG2hsL3ZxOtV2olVnzBOzq-TMLrHLomOy3FiU4NE/edit?usp=sharing) -[[Video]](https://youtu.be/0FjKkTJO0Rc?t=3737) - -### Google Cloud Tech - -Healthcare Imaging with Cloud Healthcare API -[[Video]](https://www.youtube.com/watch?v=2MiX9ScHFhY) - -## 2020 - -### OHIF ITCR Pitch - -OHIF pitch for Informatics Technology for Cancer Research (ITCR) -[[Slides]](https://docs.google.com/presentation/d/1MZXnZrVAnjmhVIWqC-aRSvJOoMMRLhLddACdCa1TybM/edit?usp=sharing) -[[Video]](https://vimeo.com/678769373/625bdb8793) - -## 2019 - -### OHIF and VTK.js Training Course - -OHIF and Kitware collaboration to create a training course for OHIF and VTK.js -developers. Funding for this work was provided by Kitware (NIH NINDS -R44NS081792, NIH NINDS R42NS086295, NIH NIBIB and NIGMS R01EB021396, NIH NIBIB -R01EB014955), Isomics (NIH P41 EB015902), and Massachusetts General Hospital -(NIH U24 CA199460). - -1. Introduction to VTK.js and OHIF - [[Slides]](https://docs.google.com/presentation/d/1NCJxpfx_qUGJI_2DhbECzaOg0k-Z6b65QlUptCofN-A/edit#slide=id.p) - [[Video]](https://vimeo.com/375520781) -2. Developing with VTK.js - [[Slides]](https://docs.google.com/presentation/d/17TCS6EhFi6SWFIrcAJ-DFdFzFFL-WD9BBTv-owmMdDU/edit#slide=id.p) - [[Video]](https://vimeo.com/375521036) -3. VTK.js Architecture and Tooling - [[Slides]](https://docs.google.com/presentation/d/1Sr1OGxMSw0oCt46koKQbmwSIE11Kqq8MGtyW3W0ASpk/edit?usp=gmail_thread) - [[Video]](https://vimeo.com/375521810) -4. OHIF + VTK.js Integration - [[Slides]](https://docs.google.com/presentation/d/1Iwg-u01HGVf1CgC6NbcBD3gm3uHN9WhjU59FSz55TN8/edit?ts=5d9c9ce4#slide=id.g59aa99cda4_0_131) - [[Video]](https://vimeo.com/375521206) - -## 2017 - -### Lesion Tracker - -LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer -Imaging Research and Clinical Trials. This project was supported in part by -grant U24 CA199460 from the National Cancer Institute (NCI) Informatics -Technology for Cancer Research (ITCR) Program. -[[Video]](https://www.youtube.com/watch?v=gUIPtoSBL-Q) - -### OHIF Community Meeting - June - -[[Slides]](https://docs.google.com/presentation/d/1K9Y6eP5DYTXoDlfwCZE6GkCUp83AK4_40YQS0dlzVBo/edit?usp=sharing) - -## 2016 - -### Imaging Community Call - -Open Source Oncology Web Viewer; Presentation by Gordon J. Harris -[[Slides]](https://www.slideshare.net/imgcommcall/lesiontracker) - -### OHIF Community Meeting - June - -[[Slides]](https://docs.google.com/presentation/d/1Ai25mBG0ZWUPhaadp3VnbCVmkYs9K51sQ8osMixrvJ0/edit?usp=sharing) - -### OHIF Community Meeting - September - -[[Slides]](https://docs.google.com/presentation/d/1iYZoU7v7KHSLHiKwH1_9_wweAkG7RGnyxrWeeHva4zQ/edit?usp=sharing) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json deleted file mode 100644 index 68ea78e7844..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "User Guide", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/index.md deleted file mode 100644 index 52f44bad8cb..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/index.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Study List ---- - -# Study List - -## Overview - -The first page you will see when the viewer is loaded is the `Study List`. In -this page you can explore all the studies that are stored on the configured -server for the `OHIF Viewer`. - -![user-study-list](../assets/img/user-study-list.png) - -## Sorting - -When the Study List is opened, the application queries the PACS for 101 studies -by default. If there are greater than 100 studies returned, the default sort for -the study list is dictated by the image archive that hosts these studies for the -viewer and study list sorting will be disabled. If there are less than or equal -to 100 studies returned, they will be sorted by study date (most recent to -oldest) and study list sorting will be enabled. Whenever a query returns greater -than 100 studies, use filters to narrow results below 100 studies to enable -Study List sorting. - -## Filters - -There are certain filters that can be used to limit the study list to the -desired criteria. - -- Patient Name: Searches between patients names -- MRN: Searches between patients Medical Record Number -- Study Date: Filters the date of the acquisition -- Description: Searches between study descriptions -- Modality: Filters the modalities -- Accession: Searches between patients accession number - -An example of using study list filter is shown below: - -![user-study-filter](../assets/img/user-study-filter.png) - -Below the study list are pagination options for 25, 50, or 100 studies per page. - -![user-study-next](../assets/img/user-study-next.png) - -## Study Summary - -Click on a study to expand the study summary panel. - -![user-study-summary](../assets/img/user-study-summary.png) - -A summary of series available in the study is shown, which contains the series -description, series number, modality of the series, instances in the series, and -buttons to launch viewer modes to display the study. - -## Study Specific Modes - -All available modes are seen in the study expanded view. Modes can be enabled or -disabled for a study based on the modalities contained within the study. - -In the screenshot below, there are two modes shown for the selected study - -- Basic Viewer: Default mode that enables rendering and measurement tracking - -- PET/CT Fusion: Mode for visualizing the PET CT study in a 3x3 format. - -Based on the mode configurations (e.g., available modalities), PET/CT mode is -disabled for studies that do not contain PET AND CT images. - - - -![user-studyist-modespecific](../assets/img/user-studyist-modespecific.png) - -The previous screenshot shows a study containing PET and CT images and both -Basic Viewer and PET/CT Mode are available. - -## View Study - -The `Basic Viewer` mode is available for all studies by default. Click on the -mode button to launch the viewer. - -![user-open-viewer](../assets/img/user-open-viewer.png) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md deleted file mode 100644 index 9032ecc5f62..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -sidebar_position: 8 ---- - -# Language - -OHIF supports internationalization capabilities and setting the general language -of the Viewer. - -It should be noted that we don't have complete translations for all the components -and all the languages; however, you can easily add the key value translation pairs -following developer guides. - -Summary of language changing usage can be seen below: - - - -## Overview Video - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json deleted file mode 100644 index 417861dba3d..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Basic Viewer", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md deleted file mode 100644 index 5d8aac1fb47..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -sidebar_position: 7 ---- - -# Hotkeys - -To open the hotkey assignment panel, you can click on the Preferences gear on the -top right side of the viewer. - - -Below, you can see the default hotkeys key bindings: - -![user-hotkeys-default](../../assets/img/user-hotkeys-default.png) - -Hotkeys can be assigned to custom bindings that persist for the duration of the browser session. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md deleted file mode 100644 index 6918ce3b1b8..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - - -# Overview -When you open a mode, viewport, toolbar and panels of the mode get shown. -It is important to note that each mode has a different UI, which serves its purpose. -Here we explain various components of `Basic Viewer` mode which includes measurement -tracking functionalities. - -Basic viewer mode (longitudinal): - -![user-viewer](../../assets/img/user-viewer.png) - -Let's break different aspects of the viewer to the main components: - -- Left Panel (study panel): displays series thumbnails with series details -- Viewport: renders the image and displays annotations -- Right Panel (measurements): displays annotations details -- Toolbar: displays tools and logo - -![user-viewer-components](../../assets/img/overview.png) - - - - -Now, we explain each component and its sub-elements in detail. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md deleted file mode 100644 index b760bf37e46..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Measurement Panel - -## Introduction -In `Basic Viewer` mode, the right panel is the `Measurement Panel`. The Measurement Panel can be expanded or hidden by clicking on the arrow to the left of `Measurements`. - -Select a measurement tool and mark an image to initiate measurement tracking. A pop-up will ask if you want to track measurements for the series on which the annotation was drawn. - -![user-measurement-panel-modal](../../assets/img/measurement-panel-prompt.png) - - - - - -If you select `Yes`, the series becomes a `tracked series`, and the current drawn measurement and next measurements are shown on the measurement panel on the right. - -![user-measurement-panel-tracked](../../assets/img/measurement-panel-tracked.png) - -If you select `No`, the measurement becomes temporary. The next annotation made will repeat the measurement tracking prompt. - -If you select `No, do not ask again`, all annotations made on the study will be temporary. - -![measurement-temporary](../../assets/img/measurement-temporary.png) - - -## Labeling Measurements -You can edit the measurement name by hovering over the measurement and selecting the edit icon. You can also label or relabel a measurement by right-clicking on it in the viewport. - -![user-measurement-edit](../../assets/img/measurement-panel-1.png) - - - -## Deleting a Measurement -A measurement can be deleting by dragging it outside the image in the viewport or by right-clicking on the measurement in the viewport and selecting 'Delete'. - - -## Jumping to a Measurement -Measurement navigation inside the top viewport can be used to move to previous and next measurement. - - -![measurements-prevNext](../../assets/img/measurements-prevNext.png) - -If a series containing a measurement is currently being displayed in a viewport, you can jump to display the measurement in the viewport by clicking on it in the Measurement Panel. - -## Export Measurements - -You can export the measurements by clicking on the `Export`. A CSV file will get downloaded to your local computer containing the drawn measurements. - - -![user-measurement-export](../../assets/img/user-measurement-export.png) - - -If you have set up your DICOM server to be able to store instances from the viewer, then you are able to create a report by clicking on the `Create Report`. -This will create a DICOM Structured Report (SR) from the measurements and push it -to the server. - -For instance, running the Viewer on a local DCM4CHEE: - - - -
- -
- -## Overview Video -An overview of measurement drawing and exporting can be seen below: - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md deleted file mode 100644 index a15f7e5dfb6..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Measurement Tracking - -## Introduction -OHIF-V3's `Basic Viewer` implements a `Measurement Tracking` workflow. Measurement -tracking allows you to: - -- Draw annotations and have them shown in the measurement panel -- Create a report from the tracked measurement and export them as DICOM SR -- Use already exported DICOM SR to re-hydrate the measurements in the viewer - - -## Status Icon -Each viewport has a left icon indicating whether the series within the viewport -contains: - -- tracked measurement OR -- untracked measurement OR -- Structured Report OR -- Locked (uneditable) Structured Report - -In the following, we will discuss each category. - -### Tracked vs Untracked Measurements - -`OHIF-v3` implements a workflow for measurement tracking that can be seen below. - -![user-measurement-panel-modal](../../assets/img/tracking-workflow1.png) - -In summary, when you create an annotation, a prompt will be shown whether to start tracking or not. If you start the tracking, the annotation style will change to a solid line, and annotation details get displayed on the measurement panel. -On the other hand, if you decline the tracking prompt, the measurement will be considered "temporary," and annotation style remains as a dashed line and not shown on the right panel, and cannot be exported. - - -Below, you can see different icons that appear for a tracked vs. untracked series in -`OHIF-v3`. - -![tracked-not-tracked](../../assets/img/tracked-not-tracked.png) - - - -#### Overview video for starting the tracking for measurements: - - -
- -
- - -

- -#### Overview video for not starting tracking for measurements: - - -
- -
- - -### Reading and Writing DICOM SR - -`OHIF-v3` provides full support for reading, writing and mapping the DICOM Structured -Report (SR) to interactable `Cornerstone Tools`. When you load an already exported -DICOM SR into the viewer, you will be prompted whether to track the measurements -for the series or not. - -![SR-exported](../../assets/img/SR-exported.png) - -If you click Yes, DICOM SR measurements gets re-hydrated into the viewer and -the series become a tracked series. However, If you say no and later decide to say track the measurements, you can always click on the SR button that will prompt you -with the same message again. - -![restore-exported-sr](../../assets/img/restore-exported-sr.png) - -The full workflow for saving measurements to SR and loading SR into the viewer is shown below. - -![user-measurement-panel-modal](../../assets/img/tracking-workflow2.png) -![user-measurement-panel-modal](../../assets/img/tracking-workflow3.png) - - -#### Overview video for loading DICOM SR and making a tracked series: - - -
- -
- -

- -#### Overview video for loading DICOM SR and not making a tracked series: - - -
- -
- -

- -
- -
- -### Loading DICOM SR into an Already Tracked Series - -If you have an already tracked series and try to load a DICOM SR measurements, -you will be shown the following lock icon. This means that, you can review the -DICOM SR measurement, manipulate image and draw "temporary" measurements; however, -you cannot edit the DICOM SR measurement. - - -![locked-sr](../../assets/img/locked-sr.png) - -

- - -#### Overview video for loading DICOM SR inside an already tracked series: - - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md deleted file mode 100644 index 1b5190ffaeb..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Study Panel - -In `Basic Viewer` mode, the left panel includes Studies related to the current -patient. You can see three main type of studies below - -- Primary: The opened study from the study list. This study is always expanded - by default. -- Recent: All studies for the patient that contain study dates within 1 year of - the primary study -- All: All studies available for the patient contained within the source - repository - -The `Study Panel` displays the measurement tracking status of each series within -a study. As you can see in the first picture, the dashed circle on the left side -of each series demonstrates whether the series is being tracked for measurement -or not. - - - -![user-study-panel](../../assets/img/user-study-panel.png) - -Studies can be expanded or collapsed by clicking on the study information in the -Study Panel. If a series is being tracking within a study, the Measurement Panel -will display this information while the study is collapsed. - - - - diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md deleted file mode 100644 index cb982ce25b8..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 6 ---- - - -# Toolbar - -The four main components of the toolbar are: - -- Navigation back to the [Study List](../index.md) -- Logo and white labelling -- [Tools](#tools) -- [Preferences](#preferences) - -![user-viewer-toolbar](../../assets/img/user-viewer-toolbar.png) - - -## Tools -This section displays all the available tools inside the mode. -## Measurement tools -The basic viewer comes with the following default measurement tools: - -- Length Tool: Calculates the linear distance between two points in *mm* -- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm* -- Annotation: Used to create a qualitative marker with a freetext label -- Ellipse: Measures an elliptical area in *mm2* and Hounsfield Units (HU) -- Calibration Tool: Calibrate (or override) the Pixel Spacing Attribute (Physical distance in the patient between the center of each pixel, specified by a numeric pair - adjacent row spacing (delimiter) adjacent column spacing in mm) - -When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool. - - -![user-viewer-toolbar-measurements](../../assets/img/user-viewer-toolbar-measurements.png) - - -## Window/Level -The `Window/Level` tool enables manipulating the window level and window width of the rendered image. Click on the tool to enable freeform adjustment, then click and drag on the viewport to freely adjust the window/level. - -Click on the caret to expand the tool and choose from predefined W/L settings for common imaging scenarios. - - -![user-toolbar-preset](../../assets/img/user-toolbar-preset.png) - - -## Pan and Zoom -With the Zoom tool selected, click and drag the cursor on an image to adjust the zoom. The magnification level is displayed in the viewport. - -With the Pan tool selected, click and drag the cursor on an image to adjust the image position. - -## Image Capture -Click on the Camera icon to download a high quality image capture using common image formats (png, jpg) - -![user-toolbar-download-icon](../../assets/img/user-toolbar-download-icon.png) - -In the opened modal, the filename, image's width and height, and filetype and can be configured before downloading the image to your local computer. - -![user-toolbarDownload](../../assets/img/user-toolbarDownload.png) - - - -## Layout Selector -Please see the `Viewport` section for details. - - -## More Tools Menu -- Reset View: Resets all image manipulation such as position, zoom, and W/L -- Rotate Right: Flips the image 90 degrees clockwise -- Flip Horizontally: Flips the image 180 degrees horizontally -- Stack Scroll: Links all viewports containing images to scroll together -- Magnify: Click on an image to magnify a particular area of interest -- Invert: Inverts the color scale -- Cine: Toggles the Cine player control in the currently selected viewport. Click the `x` on the Cine player or click the tool again to toggle off. -- Angle: Measures an adjustable angle on an image -- Probe: Drag the probe to see pixel values -- Rectangle: Measures a rectangular area in mm^2 and HU - -When a tool is selected from the `More Tools` menu, it becomes the active tool until it is replaced by clicking on a different tool in the More Tools menu or main toolbar. - - -## Overview Video -An overview of tool usage can been seen below: - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md deleted file mode 100644 index 173728ca153..00000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Viewport - -Image visualization happens at the viewport which contains canvas or canvases that -renders series. - -![user-viewer-main](../../assets/img/user-viewer-main.png) - - -By default, you can modify: - -- Zoom: right click dragging up or down -- Contrast/brightness: left click dragging up/down to change contrast, and left/right for changing brightness -- Pan: middle click dragging - - -## Changing Series for display -To change the displayed series, you can drag and drop the desired series from the left panel. Start, by dragging the thumbnail of the series, and drop it on the viewport. - -## Changing Layout -If you click on the layout icon on the toolbar, you can use the layout selector UI. After changing the layout, you can select studies for each new viewport by dragging and dropping in to the viewport. - -After changing the layout from 1x1, you will see each viewport gets tagged by a letter, -which matches its series section in the study list. - - -![user-viewer-layout](../../assets/img/user-viewer-layout.png) - - -## Overview Video -An overview of viewport layout change, and manipulation can be seen below: - - -
- -
diff --git a/platform/docs/versioned_sidebars/version-1.0-sidebars.json b/platform/docs/versioned_sidebars/version-1.0-deprecated-sidebars.json similarity index 100% rename from platform/docs/versioned_sidebars/version-1.0-sidebars.json rename to platform/docs/versioned_sidebars/version-1.0-deprecated-sidebars.json diff --git a/platform/docs/versioned_sidebars/version-2.0-sidebars.json b/platform/docs/versioned_sidebars/version-2.0-deprecated-sidebars.json similarity index 100% rename from platform/docs/versioned_sidebars/version-2.0-sidebars.json rename to platform/docs/versioned_sidebars/version-2.0-deprecated-sidebars.json diff --git a/platform/docs/versioned_sidebars/version-3.0-sidebars.json b/platform/docs/versioned_sidebars/version-3.0-sidebars.json deleted file mode 100644 index caea0c03ba6..00000000000 --- a/platform/docs/versioned_sidebars/version-3.0-sidebars.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tutorialSidebar": [ - { - "type": "autogenerated", - "dirName": "." - } - ] -} diff --git a/platform/docs/versions.json b/platform/docs/versions.json index 8858c6ab305..fe701e38475 100644 --- a/platform/docs/versions.json +++ b/platform/docs/versions.json @@ -1 +1 @@ -["2.0"] +["2.0-deprecated", "1.0-deprecated"] diff --git a/platform/i18n/.webpack/webpack.dev.js b/platform/i18n/.webpack/webpack.dev.js index 2bc3ced0b9b..4bf848b6c5c 100644 --- a/platform/i18n/.webpack/webpack.dev.js +++ b/platform/i18n/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/platform/i18n/.webpack/webpack.prod.js b/platform/i18n/.webpack/webpack.prod.js index fe0e4e35929..330256356ed 100644 --- a/platform/i18n/.webpack/webpack.prod.js +++ b/platform/i18n/.webpack/webpack.prod.js @@ -12,8 +12,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); diff --git a/platform/i18n/CHANGELOG.md b/platform/i18n/CHANGELOG.md index 90971731437..dcd0f393888 100644 --- a/platform/i18n/CHANGELOG.md +++ b/platform/i18n/CHANGELOG.md @@ -3,6 +3,437 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + + +### Bug Fixes + +* **i18n:** display set(s) are two words for English messages ([#3711](https://github.com/OHIF/Viewers/issues/3711)) ([c3a5847](https://github.com/OHIF/Viewers/commit/c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d)) + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + + +### Features + +* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6)) + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + + +### Features + +* **locale:** Added Turkish language support (tr-TR) - Community PR ([#3695](https://github.com/OHIF/Viewers/issues/3695)) ([745050a](https://github.com/OHIF/Viewers/commit/745050a28ec7c2ef2e9a4d4e590040050b2177b2)) + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + + +### Bug Fixes + +* **translation:** Side panel translate fix ([#3156](https://github.com/OHIF/Viewers/issues/3156)) ([29748d4](https://github.com/OHIF/Viewers/commit/29748d46a14d23817dbe196e0f64363fc61a8aed)) + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/i18n + + + + + ## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.7...@ohif/i18n@0.52.8) (2020-04-07) **Note:** Version bump only for package @ohif/i18n diff --git a/platform/i18n/babel.config.js b/platform/i18n/babel.config.js index fed6f05fecd..325ca2a8ee7 100644 --- a/platform/i18n/babel.config.js +++ b/platform/i18n/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/i18n/package.json b/platform/i18n/package.json index d7dc506fc98..6d4f5024b4d 100644 --- a/platform/i18n/package.json +++ b/platform/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/i18n", - "version": "3.6.0", + "version": "3.7.0-beta.108", "description": "Internationalization library for The OHIF Viewer", "author": "OHIF", "license": "MIT", diff --git a/platform/i18n/src/config.js b/platform/i18n/src/config.js index e9e1c168536..e5e4842d979 100644 --- a/platform/i18n/src/config.js +++ b/platform/i18n/src/config.js @@ -1,6 +1,4 @@ -const debugMode = !!( - process.env.NODE_ENV !== 'production' && process.env.REACT_APP_I18N_DEBUG -); +const debugMode = !!(process.env.NODE_ENV !== 'production' && process.env.REACT_APP_I18N_DEBUG); const detectionOptions = { // order and from where user language should be detected @@ -18,7 +16,7 @@ const detectionOptions = { excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage) // optional htmlTag with lang attribute, the default is: - htmlTag: document.documentElement + htmlTag: document.documentElement, }; export { debugMode, detectionOptions }; diff --git a/platform/i18n/src/index.js b/platform/i18n/src/index.js index 482b53b7e94..b2ccc6fc3e7 100644 --- a/platform/i18n/src/index.js +++ b/platform/i18n/src/index.js @@ -140,7 +140,13 @@ i18n.initializing = initI18n(); i18n.initI18n = initI18n; i18n.addLocales = addLocales; i18n.availableLanguages = getAvailableLanguagesInfo(locales); -i18n.defaultLanguage = { label: getLanguageLabel(DEFAULT_LANGUAGE), value: DEFAULT_LANGUAGE }; -i18n.currentLanguage = () => ({ label: getLanguageLabel(i18n.language), value: i18n.language }); +i18n.defaultLanguage = { + label: getLanguageLabel(DEFAULT_LANGUAGE), + value: DEFAULT_LANGUAGE, +}; +i18n.currentLanguage = () => ({ + label: getLanguageLabel(i18n.language), + value: i18n.language, +}); export default i18n; diff --git a/platform/i18n/src/locales/ar/UserPreferencesModal.json b/platform/i18n/src/locales/ar/UserPreferencesModal.json index 06992c50895..86db2742ab5 100644 --- a/platform/i18n/src/locales/ar/UserPreferencesModal.json +++ b/platform/i18n/src/locales/ar/UserPreferencesModal.json @@ -1,3 +1,3 @@ { "No hotkeys found": "Nenhuma tecla de atalho está configurada para este aplicativo. As teclas de atalho podem ser configuradas no arquivo app-config.js do aplicativo." -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/de/AboutModal.json b/platform/i18n/src/locales/de/AboutModal.json new file mode 100644 index 00000000000..29d0b30578b --- /dev/null +++ b/platform/i18n/src/locales/de/AboutModal.json @@ -0,0 +1,14 @@ +{ + "Browser": "Browser", + "Build Number": "Build-Nummer", + "Latest Master Commits": "Letzter Master Commit", + "More details": "Mehr Details", + "Name": "Name", + "About OHIF Viewer": "Über OHIF Viewer", + "OS": "OS", + "Report an issue": "Ein Problem melden", + "Repository URL": "Repository URL", + "Value": "Wert", + "Version Information": "Informationen zur Version", + "Visit the forum": "Besuchen Sie das Forum" +} diff --git a/platform/i18n/src/locales/de/Buttons.json b/platform/i18n/src/locales/de/Buttons.json new file mode 100644 index 00000000000..9bf993f3625 --- /dev/null +++ b/platform/i18n/src/locales/de/Buttons.json @@ -0,0 +1,50 @@ +{ + "Acquired": "Akquiriert", + "Angle": "Winkel", + "Axial": "Axial", + "Bidirectional": "Bidirektional", + "Brush": "Pinsel", + "CINE": "CINE", + "Cancel": "Abbrechen", + "Circle": "Kreis", + "Clear": "Leeren", + "Coronal": "Koronal", + "Crosshairs": "Fadenkreuz", + "Download": "Download", + "Ellipse": "Ellipse", + "Elliptical": "Elliptisch", + "Flip Horizontally": "Horizontal spiegeln", + "Flip Vertically": "Vertikal spiegeln", + "Freehand": "Freihand", + "Invert": "Invertieren", + "Invert Colors": "Invertieren", + "Layout": "$t(Common:Layout)", + "Grid Layout": "Rasterlayout", + "Length": "Länge", + "Levels": "Level", + "Window Level": "Helligkeit/Kontrast", + "Magnify": "Vergrössern", + "Manual": "Manuell", + "Measurements": "Messungen", + "More": "$t(Common:More)", + "Next": "$t(Common:Next)", + "Pan": "Schwenken", + "Play": "$t(Common:Play)", + "Previous": "$t(Common:Previous)", + "Probe": "Probe", + "ROI Window": "ROI Fenster", + "Rectangle": "Rechteck", + "Reset": "$t(Common:Reset)", + "Reset View": "$t(Common:Reset)", + "Reset to Defaults": "Auf Default zurücksetzen", + "Rotate Right": "Nach rechts drehen", + "Rotate +90": "Drehen +90", + "Sagittal": "Sagittal", + "Save": "Speichern", + "Stack Scroll": "Stack Scroll", + "Stop": "$t(Common:Stop)", + "Themes": "Themen", + "Zoom": "Zoomen", + "More Tools": "Weitere Werkzeuge", + "More Measure Tools": "Weitere Messwerkzeuge" +} diff --git a/platform/i18n/src/locales/de/CineDialog.json b/platform/i18n/src/locales/de/CineDialog.json new file mode 100644 index 00000000000..fe1554e6a9f --- /dev/null +++ b/platform/i18n/src/locales/de/CineDialog.json @@ -0,0 +1,8 @@ +{ + "Next image": "Nächstes Bild", + "Play / Stop": "$t(Common:Play) / $t(Common:Stop)", + "Previous image": "Vorheriges Bild", + "Skip to first image": "Zum ersten Bild springen", + "Skip to last image": "Zum letzten Bild springen", + "fps": "fps" +} diff --git a/platform/i18n/src/locales/de/Common.json b/platform/i18n/src/locales/de/Common.json new file mode 100644 index 00000000000..4e4423c62ad --- /dev/null +++ b/platform/i18n/src/locales/de/Common.json @@ -0,0 +1,16 @@ +{ + "Close": "Schliessen", + "Image": "Bild", + "Layout": "Layout", + "Measurements": "Messungen", + "More": "Mehr", + "Next": "Nächste", + "Play": "Abspielen", + "Previous": "Vorherige", + "Reset": "Zurücksetzen", + "RowsPerPage": "Zeilen pro Seite", + "Series": "Serien", + "Show": "Anzeigen", + "Stop": "Stoppen", + "StudyDate": "Studiendatum" +} diff --git a/platform/i18n/src/locales/de/DatePicker.json b/platform/i18n/src/locales/de/DatePicker.json new file mode 100644 index 00000000000..424da8fa05b --- /dev/null +++ b/platform/i18n/src/locales/de/DatePicker.json @@ -0,0 +1,5 @@ +{ + "Clear dates": "Daten löschen", + "End Date": "Enddatum", + "Start Date": "Startdatum" +} diff --git a/platform/i18n/src/locales/de/Header.json b/platform/i18n/src/locales/de/Header.json new file mode 100644 index 00000000000..4eff584411b --- /dev/null +++ b/platform/i18n/src/locales/de/Header.json @@ -0,0 +1,8 @@ +{ + "About": "Über", + "Back to Viewer": "Zurück zum Viewer", + "INVESTIGATIONAL USE ONLY": "NUR FÜR FORSCHUNGSZWECKE", + "Options": "Optionen", + "Preferences": "Einstellungen", + "Study list": "Studienliste" +} diff --git a/platform/i18n/src/locales/de/MeasurementTable.json b/platform/i18n/src/locales/de/MeasurementTable.json new file mode 100644 index 00000000000..06777092afd --- /dev/null +++ b/platform/i18n/src/locales/de/MeasurementTable.json @@ -0,0 +1,9 @@ +{ + "Criteria nonconformities": "Kriterien für Nichtkonformitäten", + "Delete": "Löschen", + "Description": "Beschreibung", + "MAX": "MAX", + "NonTargets": "NonTargets", + "Relabel": "Relabel", + "Targets": "Targets" +} diff --git a/platform/i18n/src/locales/de/StudyList.json b/platform/i18n/src/locales/de/StudyList.json new file mode 100644 index 00000000000..2794ba89386 --- /dev/null +++ b/platform/i18n/src/locales/de/StudyList.json @@ -0,0 +1,13 @@ +{ + "AccessionNumber": "Eingangsnummer", + "Accession": "Eingangsnummer", + "Empty": "Leer", + "MRN": "MRN", + "Modality": "Modalität", + "Patient Name": "Patientenname", + "Study date": "Studiendatum", + "Description": "Beschreibung", + "Study list": "Studienliste", + "Instances": "Instanzen", + "Studies": "Studien" +} diff --git a/platform/i18n/src/locales/de/UserPreferencesModal.json b/platform/i18n/src/locales/de/UserPreferencesModal.json new file mode 100644 index 00000000000..4ae672cbf7e --- /dev/null +++ b/platform/i18n/src/locales/de/UserPreferencesModal.json @@ -0,0 +1,11 @@ +{ + "Cancel": "$t(Buttons:Cancel)", + "No hotkeys found": "Keine Hotkeys gefunden.", + "Reset to Defaults": "$t(Buttons:Reset to Defaults)", + "ResetDefaultMessage": "Einstellungen zurückgesetzt. Bitte speichern.", + "Save": "$t(Buttons:Save)", + "SaveMessage": "Gespeichert", + "User Preferences": "Benutzereinstellungen", + "Language": "Sprache", + "General": "Allgemein" +} diff --git a/platform/i18n/src/locales/de/ViewportDownloadForm.json b/platform/i18n/src/locales/de/ViewportDownloadForm.json new file mode 100644 index 00000000000..cceb825e9a8 --- /dev/null +++ b/platform/i18n/src/locales/de/ViewportDownloadForm.json @@ -0,0 +1,14 @@ +{ + "emptyFilenameError": "Der Dateiname darf nicht leer sein.", + "fileType": "Dateityp", + "filename": "Dateiname", + "formTitle": "Bitte geben Sie die Grösse, den Dateinamen und den gewünschten Typ für das Bild an.", + "imageHeight": "Höhe (px)", + "imagePreview": "Vorschau", + "imageWidth": "Breite (px)", + "keepAspectRatio": "Seitenverhältnis beibehalten", + "loadingPreview": "Vorschau laden...", + "minHeightError": "Die Mindesthöhe beträgt 100px.", + "minWidthError": "Die Mindestbreite beträgt 100px.", + "showAnnotations": "Annotationen anzeigen" +} diff --git a/platform/i18n/src/locales/de/index.js b/platform/i18n/src/locales/de/index.js new file mode 100644 index 00000000000..a265fa42add --- /dev/null +++ b/platform/i18n/src/locales/de/index.js @@ -0,0 +1,25 @@ +import AboutModal from './AboutModal.json'; +import Buttons from './Buttons.json'; +import CineDialog from './CineDialog.json'; +import Common from './Common.json'; +import DatePicker from './DatePicker.json'; +import Header from './Header.json'; +import MeasurementTable from './MeasurementTable.json'; +import StudyList from './StudyList.json'; +import UserPreferencesModal from './UserPreferencesModal.json'; +import ViewportDownloadForm from './ViewportDownloadForm.json'; + +export default { + de: { + AboutModal, + Buttons, + CineDialog, + Common, + DatePicker, + Header, + MeasurementTable, + StudyList, + UserPreferencesModal, + ViewportDownloadForm, + }, +}; diff --git a/platform/i18n/src/locales/en-US/AboutModal.json b/platform/i18n/src/locales/en-US/AboutModal.json index 5ee8b5e2421..55e0144c903 100644 --- a/platform/i18n/src/locales/en-US/AboutModal.json +++ b/platform/i18n/src/locales/en-US/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Value", "Version Information": "Version Information", "Visit the forum": "Visit the forum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Buttons.json b/platform/i18n/src/locales/en-US/Buttons.json index 7a0b6578313..7e5f23b36aa 100644 --- a/platform/i18n/src/locales/en-US/Buttons.json +++ b/platform/i18n/src/locales/en-US/Buttons.json @@ -1,11 +1,14 @@ { "Acquired": "Acquired", "Angle": "Angle", + "Annotation": "Annotation", "Axial": "Axial", "Bidirectional": "Bidirectional", "Brush": "Brush", + "Cine": "Cine", "CINE": "CINE", "Cancel": "Cancel", + "Capture": "Capture", "Circle": "Circle", "Clear": "Clear", "Coronal": "Coronal", @@ -14,8 +17,10 @@ "Ellipse": "Ellipse", "Elliptical": "Elliptical", "Flip H": "Flip H", + "Flip Horizontally": "Flip Horizontally", "Flip V": "Flip V", "Freehand": "Freehand", + "Grid Layout": "Grid Layout", "Invert": "Invert", "Layout": "$t(Common:Layout)", "Length": "Length", @@ -24,6 +29,7 @@ "Manual": "Manual", "Measurements": "Measurements", "More": "$t(Common:More)", + "More Tools": "More Tools", "Next": "$t(Common:Next)", "Pan": "Pan", "Play": "$t(Common:Play)", @@ -31,13 +37,15 @@ "Probe": "Probe", "ROI Window": "ROI Window", "Rectangle": "Rectangle", + "Reference Lines": "Reference Lines", "Reset": "$t(Common:Reset)", "Reset to Defaults": "$t(Common:Reset) to Defaults", "Rotate Right": "Rotate Right", "Sagittal": "Sagittal", "Save": "Save", "Stack Scroll": "Stack Scroll", + "Stack Image Sync": "Stack Image Sync", "Stop": "$t(Common:Stop)", "Themes": "Themes", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/CineDialog.json b/platform/i18n/src/locales/en-US/CineDialog.json index b9f6667a755..48398269ce5 100644 --- a/platform/i18n/src/locales/en-US/CineDialog.json +++ b/platform/i18n/src/locales/en-US/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Skip to first $t(Common:Image)", "Skip to last image": "Skip to last $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Common.json b/platform/i18n/src/locales/en-US/Common.json index ff2e5d3b170..2a9b748c32a 100644 --- a/platform/i18n/src/locales/en-US/Common.json +++ b/platform/i18n/src/locales/en-US/Common.json @@ -6,6 +6,7 @@ "Measurements": "Measurements", "More": "More", "Next": "Next", + "NoStudyDate": "No Study Date", "Play": "Play", "Previous": "Previous", "Reset": "Reset", diff --git a/platform/i18n/src/locales/en-US/DataSourceConfiguration.json b/platform/i18n/src/locales/en-US/DataSourceConfiguration.json new file mode 100644 index 00000000000..092ea55147e --- /dev/null +++ b/platform/i18n/src/locales/en-US/DataSourceConfiguration.json @@ -0,0 +1,24 @@ +{ + "Configure Data Source": "Configure Data Source", + "Data set": "Data set", + "DICOM store": "DICOM store", + "Location": "Location", + "Project": "Project", + "Error fetching Data set list": "Error fetching data sets", + "Error fetching DICOM store list": "Error fetching DICOM stores", + "Error fetching Location list": "Error fetching locations", + "Error fetching Project list": "Error fetching projects", + "No Project available": "No projects available", + "No Location available": "No locations available", + "No Data set available": "No data sets available", + "No DICOM store available": "No DICOM stores available", + "Select": "Select", + "Search Data set list": "Search data sets", + "Search DICOM store list": "Search DICOM stores", + "Search Location list": "Search locations", + "Search Project list": "Search projects", + "Select Data set": "Select a data Set", + "Select DICOM store": "Select a DICOM store", + "Select Location": "Select a location", + "Select Project": "Select a project" +} diff --git a/platform/i18n/src/locales/en-US/DatePicker.json b/platform/i18n/src/locales/en-US/DatePicker.json index e2eb258dec6..24187a1cf42 100644 --- a/platform/i18n/src/locales/en-US/DatePicker.json +++ b/platform/i18n/src/locales/en-US/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Clear dates", "End Date": "End Date", "Start Date": "Start Date" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/MeasurementTable.json b/platform/i18n/src/locales/en-US/MeasurementTable.json index ab89cebcf84..9eb9b8d609d 100644 --- a/platform/i18n/src/locales/en-US/MeasurementTable.json +++ b/platform/i18n/src/locales/en-US/MeasurementTable.json @@ -6,4 +6,4 @@ "NonTargets": "NonTargets", "Relabel": "Relabel", "Targets": "Targets" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Messages.json b/platform/i18n/src/locales/en-US/Messages.json new file mode 100644 index 00000000000..130d2f97015 --- /dev/null +++ b/platform/i18n/src/locales/en-US/Messages.json @@ -0,0 +1,15 @@ +{ + "1": "No valid instances found in series.", + "2": "Display set has missing position information.", + "3": "Display set is not a reconstructable 3D volume.", + "4": "Multi frame display sets do not have pixel measurement information.", + "5": "Multi frame display sets do not have orientation information.", + "6": "Multi frame display sets do not have position information.", + "7": "Display set has missing frames.", + "8": "Display set has irregular spacing.", + "9": "Display set has inconsistent dimensions between frames.", + "10": "Display set has frames with inconsistent number of components.", + "11": "Display set has frames with inconsistent orientations.", + "12": "Display set has inconsistent position information.", + "13": "Unsupported display set." +} diff --git a/platform/i18n/src/locales/en-US/UserPreferencesModal.json b/platform/i18n/src/locales/en-US/UserPreferencesModal.json index 52b0dc16731..33b331f598e 100644 --- a/platform/i18n/src/locales/en-US/UserPreferencesModal.json +++ b/platform/i18n/src/locales/en-US/UserPreferencesModal.json @@ -6,4 +6,4 @@ "Save": "$t(Buttons:Save)", "SaveMessage": "Preferences saved", "User Preferences": "User Preferences" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/ViewportDownloadForm.json b/platform/i18n/src/locales/en-US/ViewportDownloadForm.json index cd1624b4b19..85001a42190 100644 --- a/platform/i18n/src/locales/en-US/ViewportDownloadForm.json +++ b/platform/i18n/src/locales/en-US/ViewportDownloadForm.json @@ -11,4 +11,4 @@ "minHeightError": "The minimum valid height is 100px.", "minWidthError": "The minimum valid width is 100px.", "showAnnotations": "Show Annotations" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/index.js b/platform/i18n/src/locales/en-US/index.js index 279856d0ad4..d8b19e6f84f 100644 --- a/platform/i18n/src/locales/en-US/index.js +++ b/platform/i18n/src/locales/en-US/index.js @@ -2,6 +2,7 @@ import AboutModal from './AboutModal.json'; import Buttons from './Buttons.json'; import CineDialog from './CineDialog.json'; import Common from './Common.json'; +import DataSourceConfiguration from './DataSourceConfiguration.json'; import DatePicker from './DatePicker.json'; import Header from './Header.json'; import MeasurementTable from './MeasurementTable.json'; @@ -10,6 +11,7 @@ import StudyBrowser from './StudyBrowser.json'; import StudyList from './StudyList.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import ViewportDownloadForm from './ViewportDownloadForm.json'; +import Messages from './Messages.json'; export default { 'en-US': { @@ -17,6 +19,7 @@ export default { Buttons, CineDialog, Common, + DataSourceConfiguration, DatePicker, Header, MeasurementTable, @@ -25,5 +28,6 @@ export default { StudyList, UserPreferencesModal, ViewportDownloadForm, + Messages, }, }; diff --git a/platform/i18n/src/locales/es/Buttons.json b/platform/i18n/src/locales/es/Buttons.json index b8841fa1863..d7e0acfbfbc 100644 --- a/platform/i18n/src/locales/es/Buttons.json +++ b/platform/i18n/src/locales/es/Buttons.json @@ -40,4 +40,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Temas", "Zoom": "Ampliar" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/CineDialog.json b/platform/i18n/src/locales/es/CineDialog.json index fff13fd0433..a9cbf4f8fe1 100644 --- a/platform/i18n/src/locales/es/CineDialog.json +++ b/platform/i18n/src/locales/es/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Ir a la primera $t(Common:Image)", "Skip to last image": "Ir a la última $t(Common:Image)", "fps": "imágenes/seg." -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/Common.json b/platform/i18n/src/locales/es/Common.json index bed045de3aa..65481c8b23c 100644 --- a/platform/i18n/src/locales/es/Common.json +++ b/platform/i18n/src/locales/es/Common.json @@ -12,4 +12,4 @@ "Show": "Mostrar", "Stop": "Detener", "StudyDate": "Fecha de estudo" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/Header.json b/platform/i18n/src/locales/es/Header.json index d2c4bb59661..8a4308c5b3f 100644 --- a/platform/i18n/src/locales/es/Header.json +++ b/platform/i18n/src/locales/es/Header.json @@ -5,4 +5,4 @@ "Options": "Opciones", "Preferences": "Preferencias", "Study list": "Lista de estudios" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/UserPreferencesModal.json b/platform/i18n/src/locales/es/UserPreferencesModal.json index ca4c770964d..4534903afc9 100644 --- a/platform/i18n/src/locales/es/UserPreferencesModal.json +++ b/platform/i18n/src/locales/es/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Preferencias de Usuario" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Buttons.json b/platform/i18n/src/locales/fr/Buttons.json index 31c8d301aa3..50e7dfadc45 100644 --- a/platform/i18n/src/locales/fr/Buttons.json +++ b/platform/i18n/src/locales/fr/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Themes", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/CineDialog.json b/platform/i18n/src/locales/fr/CineDialog.json index 6135824a360..257e8a257b5 100644 --- a/platform/i18n/src/locales/fr/CineDialog.json +++ b/platform/i18n/src/locales/fr/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Retour à la première $t(Common:Image)", "Skip to last image": "Aller à la dernière $t(Common:Image)", "fps": "ips" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Common.json b/platform/i18n/src/locales/fr/Common.json index 5d197691938..e1220f51987 100644 --- a/platform/i18n/src/locales/fr/Common.json +++ b/platform/i18n/src/locales/fr/Common.json @@ -7,4 +7,4 @@ "Previous": "Précédent", "Reset": "Reset", "Stop": "Stop" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Header.json b/platform/i18n/src/locales/fr/Header.json index 88db056b8aa..e9b894874cd 100644 --- a/platform/i18n/src/locales/fr/Header.json +++ b/platform/i18n/src/locales/fr/Header.json @@ -5,4 +5,4 @@ "Options": "Options", "Preferences": "Préférences", "Study list": "Liste d'études" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/UserPreferencesModal.json b/platform/i18n/src/locales/fr/UserPreferencesModal.json index a4fc5810e13..506547fd98c 100644 --- a/platform/i18n/src/locales/fr/UserPreferencesModal.json +++ b/platform/i18n/src/locales/fr/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Préférences utilisateur" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/index.js b/platform/i18n/src/locales/index.js index b950f9ab882..25b84253f99 100644 --- a/platform/i18n/src/locales/index.js +++ b/platform/i18n/src/locales/index.js @@ -1,4 +1,6 @@ +import tr_TR from './tr-TR/'; import ar from './ar/'; +import de from './de'; import en_US from './en-US/'; import es from './es/'; import fr from './fr/'; @@ -11,6 +13,8 @@ import test_lng from './test-LNG/'; export default { ...ar, + ...tr_TR, + ...de, ...en_US, ...es, ...fr, @@ -19,5 +23,5 @@ export default { ...pt_BR, ...vi, ...zh, - ...test_lng + ...test_lng, }; diff --git a/platform/i18n/src/locales/ja-JP/Buttons.json b/platform/i18n/src/locales/ja-JP/Buttons.json index 5b35d263f5c..aed8434026f 100644 --- a/platform/i18n/src/locales/ja-JP/Buttons.json +++ b/platform/i18n/src/locales/ja-JP/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "テーマ", "Zoom": "ズーム" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/CineDialog.json b/platform/i18n/src/locales/ja-JP/CineDialog.json index 5dc36b5315a..be38a7bae7f 100644 --- a/platform/i18n/src/locales/ja-JP/CineDialog.json +++ b/platform/i18n/src/locales/ja-JP/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "$t(Common:Image)最初にスキップ", "Skip to last image": "$t(Common:Image)最後にスキップ", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/Common.json b/platform/i18n/src/locales/ja-JP/Common.json index cec88db8933..fa56db25b24 100644 --- a/platform/i18n/src/locales/ja-JP/Common.json +++ b/platform/i18n/src/locales/ja-JP/Common.json @@ -7,4 +7,4 @@ "Previous": "前へ", "Reset": "リセット", "Stop": "ストップ" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/Header.json b/platform/i18n/src/locales/ja-JP/Header.json index 55f344e2215..1562950ad73 100644 --- a/platform/i18n/src/locales/ja-JP/Header.json +++ b/platform/i18n/src/locales/ja-JP/Header.json @@ -5,4 +5,4 @@ "Options": "オプション", "Preferences": "プレファレンス", "Study list": "スタディリスト" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json b/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json index df544cfa03a..b7215966cbe 100644 --- a/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json +++ b/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "ユーザプレファレンス" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Buttons.json b/platform/i18n/src/locales/nl/Buttons.json index 6748f63f0f4..0443cffd184 100644 --- a/platform/i18n/src/locales/nl/Buttons.json +++ b/platform/i18n/src/locales/nl/Buttons.json @@ -3,4 +3,4 @@ "More": "Meer", "Pan": "Pan", "Zoom": "Inzoomen" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Common.json b/platform/i18n/src/locales/nl/Common.json index 5e64c603955..d35764a7f41 100644 --- a/platform/i18n/src/locales/nl/Common.json +++ b/platform/i18n/src/locales/nl/Common.json @@ -1,3 +1,3 @@ { "More": "Meer" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Header.json b/platform/i18n/src/locales/nl/Header.json index 482d3c72e7e..38995e7b628 100644 --- a/platform/i18n/src/locales/nl/Header.json +++ b/platform/i18n/src/locales/nl/Header.json @@ -4,4 +4,4 @@ "Options": "Opties", "Preferences": "Voorkeuren", "Study list": "Studie Overzicht" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/AboutModal.json b/platform/i18n/src/locales/pt-BR/AboutModal.json index e563303e2ed..23ccac43ab2 100644 --- a/platform/i18n/src/locales/pt-BR/AboutModal.json +++ b/platform/i18n/src/locales/pt-BR/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Valor", "Version Information": "Informação da Versão", "Visit the forum": "Visite o fórum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Buttons.json b/platform/i18n/src/locales/pt-BR/Buttons.json index 6fe4b71a5c0..0e910aed101 100644 --- a/platform/i18n/src/locales/pt-BR/Buttons.json +++ b/platform/i18n/src/locales/pt-BR/Buttons.json @@ -40,4 +40,4 @@ "Stop": "Parar", "Themes": "Temas", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/CineDialog.json b/platform/i18n/src/locales/pt-BR/CineDialog.json index edbf25be663..d4d653efa7a 100644 --- a/platform/i18n/src/locales/pt-BR/CineDialog.json +++ b/platform/i18n/src/locales/pt-BR/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Pular para a primeira imagem", "Skip to last image": "Pular para a última imagem", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Common.json b/platform/i18n/src/locales/pt-BR/Common.json index bf6631668db..e50b07e3d20 100644 --- a/platform/i18n/src/locales/pt-BR/Common.json +++ b/platform/i18n/src/locales/pt-BR/Common.json @@ -8,4 +8,4 @@ "Previous": "Anterior", "Reset": "Restaurar", "Stop": "Stop" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/DatePicker.json b/platform/i18n/src/locales/pt-BR/DatePicker.json index a8dbd03e53e..dbd86e2fc34 100644 --- a/platform/i18n/src/locales/pt-BR/DatePicker.json +++ b/platform/i18n/src/locales/pt-BR/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Limpar datas", "End Date": "Data Final", "Start Date": "Data Inicial" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Header.json b/platform/i18n/src/locales/pt-BR/Header.json index fdd6f0d8f9b..8f292e02aad 100644 --- a/platform/i18n/src/locales/pt-BR/Header.json +++ b/platform/i18n/src/locales/pt-BR/Header.json @@ -5,4 +5,4 @@ "Options": "Opções", "Preferences": "Preferências", "Study list": "Lista de estudos" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Messages.json b/platform/i18n/src/locales/pt-BR/Messages.json new file mode 100644 index 00000000000..649b86350b3 --- /dev/null +++ b/platform/i18n/src/locales/pt-BR/Messages.json @@ -0,0 +1,15 @@ +{ + "1": "Série sem imagens.", + "2": "Série nao possui informação de posição.", + "3": "Serie não é reconstruível.", + "4": "Série nulti frame não possui informação de medidas.", + "5": "Série multi frame não possui informação de orientação.", + "6": "Série multi frame não possui informação de posição.", + "7": "Série não possui algumas imagens.", + "8": "Série possui espaçamento irregular.", + "9": "Série possui dimensões inconsistentes entre frames.", + "10": "Série possui frames com componentes inconsistentes.", + "11": "Série possui frames com orientações inconsistentes.", + "12": "Série possui informação de posição inconsistentes.", + "13": "Série não suportada." +} diff --git a/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json b/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json index f87b3ec41ac..046be3c074d 100644 --- a/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json +++ b/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json @@ -5,4 +5,4 @@ "Save": "Salvar", "SaveMessage": "Preferências salvas", "User Preferences": "Preferências do Usuário" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/index.js b/platform/i18n/src/locales/pt-BR/index.js index 0695b6869fe..2dcc04c2b17 100644 --- a/platform/i18n/src/locales/pt-BR/index.js +++ b/platform/i18n/src/locales/pt-BR/index.js @@ -6,6 +6,7 @@ import DatePicker from './DatePicker.json'; import Header from './Header.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import MeasurementTable from './MeasurementTable.json'; +import Messages from './Messages.json'; export default { 'pt-BR': { @@ -17,5 +18,6 @@ export default { Header, UserPreferencesModal, MeasurementTable, + Messages, }, }; diff --git a/platform/i18n/src/locales/test-LNG/AboutModal.json b/platform/i18n/src/locales/test-LNG/AboutModal.json index 5ee8b5e2421..55e0144c903 100644 --- a/platform/i18n/src/locales/test-LNG/AboutModal.json +++ b/platform/i18n/src/locales/test-LNG/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Value", "Version Information": "Version Information", "Visit the forum": "Visit the forum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/CineDialog.json b/platform/i18n/src/locales/test-LNG/CineDialog.json index b9f6667a755..48398269ce5 100644 --- a/platform/i18n/src/locales/test-LNG/CineDialog.json +++ b/platform/i18n/src/locales/test-LNG/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Skip to first $t(Common:Image)", "Skip to last image": "Skip to last $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/DatePicker.json b/platform/i18n/src/locales/test-LNG/DatePicker.json index e2eb258dec6..24187a1cf42 100644 --- a/platform/i18n/src/locales/test-LNG/DatePicker.json +++ b/platform/i18n/src/locales/test-LNG/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Clear dates", "End Date": "End Date", "Start Date": "Start Date" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json b/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json index cd1624b4b19..85001a42190 100644 --- a/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json +++ b/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json @@ -11,4 +11,4 @@ "minHeightError": "The minimum valid height is 100px.", "minWidthError": "The minimum valid width is 100px.", "showAnnotations": "Show Annotations" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/index.js b/platform/i18n/src/locales/test-LNG/index.js index 315deb5d363..f39132793b7 100644 --- a/platform/i18n/src/locales/test-LNG/index.js +++ b/platform/i18n/src/locales/test-LNG/index.js @@ -32,6 +32,6 @@ export default { PatientInfo, Modes, SidePanel, - Modals + Modals, }, }; diff --git a/platform/i18n/src/locales/tr-TR/AboutModal.json b/platform/i18n/src/locales/tr-TR/AboutModal.json new file mode 100644 index 00000000000..487e4082c6d --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/AboutModal.json @@ -0,0 +1,14 @@ +{ + "Browser": "Tarayıcı", + "Build Number": "Derleme Numarası", + "Latest Master Commits": "Son Kaynak Kod Güncellemesi", + "More details": "Daha Fazla Detay", + "Name": "İsim", + "OHIF Viewer - About": "OHIF Viewer - Hakkında", + "OS": "İşletim Sistemi", + "Report an issue": "Sorun Bildir", + "Repository URL": "Kaynak Kod URL", + "Value": "Değer", + "Version Information": "Sürüm Bilgisi", + "Visit the forum": "Forumu ziyaret et" +} diff --git a/platform/i18n/src/locales/tr-TR/Buttons.json b/platform/i18n/src/locales/tr-TR/Buttons.json new file mode 100644 index 00000000000..83c2655c115 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/Buttons.json @@ -0,0 +1,43 @@ +{ + "Acquired": "Edinilen", + "Angle": "Açı", + "Axial": "Eksenel", + "Bidirectional": "Çift Yönlü", + "Brush": "Fırça", + "CINE": "CINE", + "Cancel": "Vazgeç", + "Circle": "Daire", + "Clear": "Temizle", + "Coronal": "Koronal", + "Crosshairs": "Kesişim", + "Download": "İndir", + "Ellipse": "Elips", + "Elliptical": "Eliptik", + "Flip H": "Çevir D", + "Flip V": "Çevir Y", + "Freehand": "Serbest El", + "Invert": "Tersini Çevir", + "Layout": "$t(Common:Layout)", + "Length": "Uzunluk", + "Levels": "Seviyeler", + "Magnify": "Büyüt", + "Manual": "Manuel", + "Measurements": "Ölçümler", + "More": "$t(Common:More)", + "Next": "$t(Common:Next)", + "Pan": "Tut", + "Play": "$t(Common:Play)", + "Previous": "$t(Common:Previous)", + "Probe": "İncele", + "ROI Window": "ROI Penceresi", + "Rectangle": "Diktörtgen", + "Reset": "$t(Common:Reset)", + "Reset to Defaults": "Varsayılana $t(Common:Reset)", + "Rotate Right": "Sağa Döndür", + "Sagittal": "Sagital", + "Save": "Kaydet", + "Stack Scroll": "Yığın Kaydırma", + "Stop": "$t(Common:Stop)", + "Themes": "Temalar", + "Zoom": "Yakınlaştır" +} diff --git a/platform/i18n/src/locales/tr-TR/CineDialog.json b/platform/i18n/src/locales/tr-TR/CineDialog.json new file mode 100644 index 00000000000..7f93001d343 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/CineDialog.json @@ -0,0 +1,8 @@ +{ + "Next image": "$t(Common:Next) $t(Common:Image)", + "Play / Stop": "$t(Common:Play) / $t(Common:Stop)", + "Previous image": "$t(Common:Previous) $t(Common:Image)", + "Skip to first image": "İlk $t(Common:Image) Geç", + "Skip to last image": "Son $t(Common:Image) Geç", + "fps": "fps" +} diff --git a/platform/i18n/src/locales/tr-TR/Common.json b/platform/i18n/src/locales/tr-TR/Common.json new file mode 100644 index 00000000000..eddaacc4349 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/Common.json @@ -0,0 +1,16 @@ +{ + "Close": "Kapat", + "Image": "Görüntü", + "Layout": "Düzen", + "Measurements": "Ölçümler", + "More": "Daha Fazla", + "Next": "Sonraki", + "Play": "Oynat", + "Previous": "Önceki", + "Reset": "Sıfırla", + "RowsPerPage": "Sayfa başına satır", + "Series": "Seriler", + "Show": "Göster", + "Stop": "Durdur", + "StudyDate": "Çalışma Zamanı" +} diff --git a/platform/i18n/src/locales/tr-TR/DatePicker.json b/platform/i18n/src/locales/tr-TR/DatePicker.json new file mode 100644 index 00000000000..ca1073dcd80 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/DatePicker.json @@ -0,0 +1,5 @@ +{ + "Clear dates": "Tarihleri Temizle", + "End Date": "Bitiş Tarih", + "Start Date": "Başlangıç Tarihi" +} diff --git a/platform/i18n/src/locales/tr-TR/Header.json b/platform/i18n/src/locales/tr-TR/Header.json new file mode 100644 index 00000000000..d4a5815bf6f --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/Header.json @@ -0,0 +1,8 @@ +{ + "About": "Hakkında", + "Back to Viewer": "Görüntüleyiciye Dön", + "INVESTIGATIONAL USE ONLY": "SADECE ARAŞTIRMA AMAÇLI KULLANIM", + "Options": "Seçenekler", + "Preferences": "Tercihler", + "Study list": "Çalışma Listesi" +} diff --git a/platform/i18n/src/locales/tr-TR/MeasurementTable.json b/platform/i18n/src/locales/tr-TR/MeasurementTable.json new file mode 100644 index 00000000000..ac89358f832 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/MeasurementTable.json @@ -0,0 +1,9 @@ +{ + "Criteria nonconformities": "Kriter uygunsuzlukları", + "Delete": "Sil", + "Description": "Açıklama", + "MAX": "Enfazla", + "NonTargets": "Hedefsiz", + "Relabel": "Tekrar Etiketle", + "Targets": "Hedefler" +} diff --git a/platform/i18n/src/locales/tr-TR/StudyList.json b/platform/i18n/src/locales/tr-TR/StudyList.json new file mode 100644 index 00000000000..4eaea20ca85 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/StudyList.json @@ -0,0 +1,10 @@ +{ + "AccessionNumber": "Accession #", + "Empty": "Boş", + "MRN": "MRN", + "Modality": "Modalite", + "PatientName": "Hasta Adı", + "StudyDate": "Çalışma Zamanı", + "StudyDescription": "Açıklama", + "StudyList": "Çalışma Listesi" +} diff --git a/platform/i18n/src/locales/tr-TR/UserPreferencesModal.json b/platform/i18n/src/locales/tr-TR/UserPreferencesModal.json new file mode 100644 index 00000000000..1a843a3a4a3 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/UserPreferencesModal.json @@ -0,0 +1,9 @@ +{ + "Cancel": "$t(Buttons:Cancel)", + "No hotkeys found": "Bu uygulama için hiçbir kısayol tuşu yapılandırılmamış. Kısayol tuşları, uygulamanın app-config.js dosyasında yapılandırılabilir.", + "Reset to Defaults": "$t(Buttons:Reset to Defaults)", + "ResetDefaultMessage": "Tercihler başarıyla varsayılana sıfırlandı.
Bu eylemi gerçekleştirmek için Kaydetmelisiniz.", + "Save": "$t(Buttons:Save)", + "SaveMessage": "Tercihler kaydedildi", + "User Preferences": "Kullanıcı tercihleri" +} diff --git a/platform/i18n/src/locales/tr-TR/ViewportDownloadForm.json b/platform/i18n/src/locales/tr-TR/ViewportDownloadForm.json new file mode 100644 index 00000000000..22ce0efcd62 --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/ViewportDownloadForm.json @@ -0,0 +1,14 @@ +{ + "emptyFilenameError": "Dosya adı boş olamaz.", + "fileType": "Dosya Tipi", + "filename": "Dosya Adı", + "formTitle": "Lütfen çıktı görüntüsü için boyutları, dosya adını ve istediğiniz türü belirtin.", + "imageHeight": "Görüntü Yüksekliği (px)", + "imagePreview": "Görüntü Önizleme", + "imageWidth": "Görüntü Genişliği (px)", + "keepAspectRatio": "En-boy oranını koru", + "loadingPreview": "Görüntü Önzilemesi Yükleniyor...", + "minHeightError": "Minimum geçerli yükseklik 100 pikseldir.", + "minWidthError": "Minimum geçerli genişlik 100 pikseldir.", + "showAnnotations": "Ek Açıklamaları Göster" +} diff --git a/platform/i18n/src/locales/tr-TR/index.js b/platform/i18n/src/locales/tr-TR/index.js new file mode 100644 index 00000000000..174822bd3cd --- /dev/null +++ b/platform/i18n/src/locales/tr-TR/index.js @@ -0,0 +1,25 @@ +import AboutModal from './AboutModal.json'; +import Buttons from './Buttons.json'; +import CineDialog from './CineDialog.json'; +import Common from './Common.json'; +import DatePicker from './DatePicker.json'; +import Header from './Header.json'; +import MeasurementTable from './MeasurementTable.json'; +import StudyList from './StudyList.json'; +import UserPreferencesModal from './UserPreferencesModal.json'; +import ViewportDownloadForm from './ViewportDownloadForm.json'; + +export default { + 'tr-TR': { + AboutModal, + Buttons, + CineDialog, + Common, + DatePicker, + Header, + MeasurementTable, + StudyList, + UserPreferencesModal, + ViewportDownloadForm, + }, +}; diff --git a/platform/i18n/src/locales/vi/Buttons.json b/platform/i18n/src/locales/vi/Buttons.json index 1d3f15b8c6e..ea378b41a80 100644 --- a/platform/i18n/src/locales/vi/Buttons.json +++ b/platform/i18n/src/locales/vi/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Giao diện", "Zoom": "Thu phóng" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/CineDialog.json b/platform/i18n/src/locales/vi/CineDialog.json index 6222d6406fa..d2e7366be53 100644 --- a/platform/i18n/src/locales/vi/CineDialog.json +++ b/platform/i18n/src/locales/vi/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Bỏ qua đến đầu $t(Common:Image)", "Skip to last image": "Bỏ qua đến cuối $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/Common.json b/platform/i18n/src/locales/vi/Common.json index 15f6d3dfeb6..5ae2c4c0257 100644 --- a/platform/i18n/src/locales/vi/Common.json +++ b/platform/i18n/src/locales/vi/Common.json @@ -12,4 +12,4 @@ "Show": "Hiển thị", "Stop": "Dừng", "StudyDate": "Ngày chụp" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/Header.json b/platform/i18n/src/locales/vi/Header.json index 6d28ae1c93a..07ffb42b916 100644 --- a/platform/i18n/src/locales/vi/Header.json +++ b/platform/i18n/src/locales/vi/Header.json @@ -5,4 +5,4 @@ "Options": "Lựa chọn", "Preferences": "Thiết lập", "Study list": "Danh sách" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/StudyList.json b/platform/i18n/src/locales/vi/StudyList.json index a818f95980c..50acf54b68f 100644 --- a/platform/i18n/src/locales/vi/StudyList.json +++ b/platform/i18n/src/locales/vi/StudyList.json @@ -7,4 +7,4 @@ "StudyDate": "Ngày chụp", "StudyDescription": "Diễn giải", "StudyList": "Danh sách" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/UserPreferencesModal.json b/platform/i18n/src/locales/vi/UserPreferencesModal.json index 087801611cc..f5474c9e5ed 100644 --- a/platform/i18n/src/locales/vi/UserPreferencesModal.json +++ b/platform/i18n/src/locales/vi/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Thiết lập theo người dùng" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/AboutModal.json b/platform/i18n/src/locales/zh/AboutModal.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/platform/i18n/src/locales/zh/AboutModal.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/platform/i18n/src/locales/zh/Buttons.json b/platform/i18n/src/locales/zh/Buttons.json index 38efe7d0874..ea57d0b3b3f 100644 --- a/platform/i18n/src/locales/zh/Buttons.json +++ b/platform/i18n/src/locales/zh/Buttons.json @@ -1,42 +1,68 @@ { "Acquired": "已获取", "Angle": "角度", + "Annotation": "注释", + "Arrow Annotate": "标注", "Axial": "轴状面", + "Bidirectional Tool": "双向", "Bidirectional": "双向", + "Bone": "骨窗", + "Brain": "脑窗", "Brush": "橡皮擦", - "CINE": "播放动画", "Cancel": "取消", + "Capture": "下载", + "CINE": "播放动画", + "Cine": "连续播放", "Circle": "圆", "Clear": "清除", "Coronal": "冠状面", "Crosshairs": "十字线", + "Dismiss Aspect": "解除Aspect", + "Ellipse Tool": "椭圆", "Ellipse": "椭圆", "Elliptical": "椭圆的", "Flip H": "左右翻转", + "Flip Horizontal": "水平翻转", + "Flip Horizontally": "左右翻转", "Flip V": "上下翻转", "Freehand": "自由画线", + "Grid Layout": "窗口布局", + "Invert Colors": "灰度反转", "Invert": "灰度反转", + "Keep Aspect": "保持Aspect", "Layout": "显示窗口", + "Length Tool": "长度", "Length": "长度", "Levels": "层级", + "Liver": "肝窗", + "Lung": "肺窗", "Magnify": "放大镜", "Manual": "手动", "Measurements": "测量", + "More Measure Tools": "更多测量工具", + "More Tools": "更多工具", "More": "更多", "Next": "下一个", "Pan": "移动", "Play": "播放", "Previous": "上一个", "Probe": "探针", - "ROI Window": "选择对比度", "Rectangle": "矩形", - "Reset": "复原", + "Reference Lines": "参考线", "Reset to Defaults": "返回默认", + "Reset View": "复原", + "Reset": "复原", + "ROI Window": "选择对比度", + "Rotate +90": "顺时针旋转", "Rotate Right": "顺时针旋转", "Sagittal": "矢状面", "Save": "保存", + "Soft tissue": "软组织窗", + "Stack Image Sync": "影像联动", "Stack Scroll": "滑动切换图层", "Stop": "停止", "Themes": "主题", + "W/L Presets": "窗位预设", + "Window Level": "窗位", "Zoom": "放大" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/CineDialog.json b/platform/i18n/src/locales/zh/CineDialog.json index e40ae5b594a..6bdce24cd18 100644 --- a/platform/i18n/src/locales/zh/CineDialog.json +++ b/platform/i18n/src/locales/zh/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "跳转到第一个图像", "Skip to last image": "跳转到最后一个图像", "fps": "帧率" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/Common.json b/platform/i18n/src/locales/zh/Common.json index 896cf558e3d..10ce931e9d1 100644 --- a/platform/i18n/src/locales/zh/Common.json +++ b/platform/i18n/src/locales/zh/Common.json @@ -12,4 +12,4 @@ "Show": "显示", "Stop": "停止", "StudyDate": "时间" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/ContextMenu.json b/platform/i18n/src/locales/zh/ContextMenu.json new file mode 100644 index 00000000000..144e5d56838 --- /dev/null +++ b/platform/i18n/src/locales/zh/ContextMenu.json @@ -0,0 +1,4 @@ +{ + "Add Label": "添加标注", + "Delete measurement": "删除测量" +} diff --git a/platform/i18n/src/locales/zh/DatePicker.json b/platform/i18n/src/locales/zh/DatePicker.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/platform/i18n/src/locales/zh/DatePicker.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/platform/i18n/src/locales/zh/Dialog.json b/platform/i18n/src/locales/zh/Dialog.json new file mode 100644 index 00000000000..7794304e52f --- /dev/null +++ b/platform/i18n/src/locales/zh/Dialog.json @@ -0,0 +1,6 @@ +{ + "Enter your annotation": "$t(Common:Enter your annotation)", + "Cancel": "$t(Common:Cancel)", + "Save": "$t(Common:Save)", + "Provide a name for your report": "输入报告名称" +} diff --git a/platform/i18n/src/locales/zh/ErrorBoundary.json b/platform/i18n/src/locales/zh/ErrorBoundary.json new file mode 100644 index 00000000000..ac9289c8c0a --- /dev/null +++ b/platform/i18n/src/locales/zh/ErrorBoundary.json @@ -0,0 +1,7 @@ +{ + "Sorry, something went wrong there. Try again.": "发生错误,请重试。", + "Context": "上下文", + "Error Message": "错误信息", + "Stack": "堆栈", + "Something went wrong": "发生错误" +} diff --git a/platform/i18n/src/locales/zh/Header.json b/platform/i18n/src/locales/zh/Header.json index 5f64c3a18a9..1ecb90e90ea 100644 --- a/platform/i18n/src/locales/zh/Header.json +++ b/platform/i18n/src/locales/zh/Header.json @@ -5,4 +5,4 @@ "Options": "选项", "Preferences": "偏好", "Study list": "研究列表" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/Local.json b/platform/i18n/src/locales/zh/Local.json new file mode 100644 index 00000000000..439189b6321 --- /dev/null +++ b/platform/i18n/src/locales/zh/Local.json @@ -0,0 +1,4 @@ +{ + "Load files": "加载文件", + "Load folders": "加载文件夹" +} diff --git a/platform/i18n/src/locales/zh/MeasurementTable.json b/platform/i18n/src/locales/zh/MeasurementTable.json index 3ff705195bd..ef6c2db7220 100644 --- a/platform/i18n/src/locales/zh/MeasurementTable.json +++ b/platform/i18n/src/locales/zh/MeasurementTable.json @@ -5,5 +5,12 @@ "MAX": "最大", "NonTargets": "非靶向", "Relabel": "重新标记", - "Targets": "靶向" -} \ No newline at end of file + "Measurements": "测量", + "Targets": "靶向", + "Export CSV": "导出CSV", + "No tracked measurements": "没有跟踪的测量值", + "Export": "导出", + "Create Report": "新建报告", + "Do you want to add this measurement to the existing report?": "将测量添加到报告中?", + "Track measurements for this series?": "要对个序列进行跟踪吗?" +} diff --git a/platform/i18n/src/locales/zh/Modals.json b/platform/i18n/src/locales/zh/Modals.json new file mode 100644 index 00000000000..c80bfae95be --- /dev/null +++ b/platform/i18n/src/locales/zh/Modals.json @@ -0,0 +1,19 @@ +{ + "Active viewport has no displayed image": "视图窗口没有图像", + "Cancel": "取消", + "Download": "下载", + "The file name cannot be empty.": "文件名称不能为空", + "File Type": "图片类型", + "File Name": "文件名", + "formTitle": "Please specify the dimensions, filename, and desired type for the output image.", + "Image height (px)": "高(px)", + "Image Preview": "预览", + "Image preview": "预览", + "Image width (px)": "宽(px)", + "keepAspectRatio": "Keep aspect ratio", + "loadingPreview": "Loading Image Preview...", + "The minimum valid height is 100px.": "图片最小高度值为100px", + "The minimum valid width is 100px.": "图片最小宽度值为100px", + "Show Annotations": "显示标注", + "Please specify the dimensions, filename, and desired type for the output image.": "请指定输出图像的尺寸、文件名和所需类型。" +} diff --git a/platform/i18n/src/locales/zh/Modes.json b/platform/i18n/src/locales/zh/Modes.json new file mode 100644 index 00000000000..9e33c9468b1 --- /dev/null +++ b/platform/i18n/src/locales/zh/Modes.json @@ -0,0 +1,5 @@ +{ + "Basic Viewer": "基础查看器", + "Total Metabolic Tumor Volume": "总代谢肿瘤体积", + "Download High Quality Image": "A" +} diff --git a/platform/i18n/src/locales/zh/Notification.json b/platform/i18n/src/locales/zh/Notification.json new file mode 100644 index 00000000000..de9c40603fc --- /dev/null +++ b/platform/i18n/src/locales/zh/Notification.json @@ -0,0 +1,18 @@ +{ + "Do you want to add this measurement to the existing report?": "添加测量值到当前报告中?", + "Create new report": "创建新报告", + "Add to existing report": "添加", + "Discard": "放弃", + "You have existing tracked measurements. What would you like to do with your existing tracked measurements?": "已经存在跟踪的测量,如何处理这些测量数据?", + "No, do not ask again for this series": "否,不再询问", + "No": "否", + "Track measurements for this series?": "对序列的测量值进行跟踪?", + "Yes": "是", + "Cancel": "取消", + "Measurements cannot span across multiple studies. Do you want to save your tracked measurements?": "测量不能跨多个检查,是否要保存跟踪的测量值?", + "No, discard previously tracked series & measurements": "否,放弃之前跟踪的序列和测量值。", + "Do you want to continue tracking measurements for this study?": "继续对该检查进行测量跟踪吗?", + "Create Report": "新建报告", + "Measurements saved successfully": "测量值保存成功", + "Failed to store measurements": "测量值保存失败" +} diff --git a/platform/i18n/src/locales/zh/PatientInfo.json b/platform/i18n/src/locales/zh/PatientInfo.json new file mode 100644 index 00000000000..6e4a57e47d0 --- /dev/null +++ b/platform/i18n/src/locales/zh/PatientInfo.json @@ -0,0 +1,8 @@ +{ + "Sex": "性别", + "Age": "年龄", + "MRN": "病例号", + "Thickness": "厚度", + "Spacing": "间距", + "Scanner": "扫描器" +} diff --git a/platform/i18n/src/locales/zh/SidePanel.json b/platform/i18n/src/locales/zh/SidePanel.json new file mode 100644 index 00000000000..11a53105a1c --- /dev/null +++ b/platform/i18n/src/locales/zh/SidePanel.json @@ -0,0 +1,6 @@ +{ + "Studies": "检查", + "Measurements": "测量", + "Measure": "测量", + "Segmentation": "分割" +} diff --git a/platform/i18n/src/locales/zh/StudyBrowser.json b/platform/i18n/src/locales/zh/StudyBrowser.json new file mode 100644 index 00000000000..a4a1e78cece --- /dev/null +++ b/platform/i18n/src/locales/zh/StudyBrowser.json @@ -0,0 +1,6 @@ +{ + "Primary": "当前", + "Recent": "最近", + "All": "全部", + "Tracked Series": "个跟踪序列" +} diff --git a/platform/i18n/src/locales/zh/StudyList.json b/platform/i18n/src/locales/zh/StudyList.json index 1c5888c7fc9..d7563ff5531 100644 --- a/platform/i18n/src/locales/zh/StudyList.json +++ b/platform/i18n/src/locales/zh/StudyList.json @@ -4,5 +4,25 @@ "PatientName": "患者姓名", "StudyDate": "检查日期", "StudyDescription": "描述", - "StudyList": "检查列表" -} \ No newline at end of file + "StudyList": "检查列表", + "Patient Name": "患者姓名", + "MRN": "病例号", + "Study date": "检查日期", + "Description": "描述", + "Study list": "检查列表", + "Clear filters": "清空条件", + "Studies": "", + "Instances": "图像数", + "Accession": "检查号", + "Results per page": "每页条数", + "< Previous": "上一页", + "Next >": "下一页", + "Page": "页码", + "Start Date": "开始日期", + "Series": "序列", + "No studies available": "没有数据", + "Loading...": "加载中...", + "Select...": "选择...", + "InstitutionName": "检查机构", + "Filter list to 100 studies or less to enable sorting": "将检查列表过滤到 100 个或更少以启用排序" +} diff --git a/platform/i18n/src/locales/zh/UserPreferencesModal.json b/platform/i18n/src/locales/zh/UserPreferencesModal.json index 865b7e33350..e71386abc67 100644 --- a/platform/i18n/src/locales/zh/UserPreferencesModal.json +++ b/platform/i18n/src/locales/zh/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "返回默认", "Save": "保存", "User Preferences": "用户偏好" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/ViewportDownloadForm.json b/platform/i18n/src/locales/zh/ViewportDownloadForm.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/platform/i18n/src/locales/zh/ViewportDownloadForm.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/platform/i18n/src/locales/zh/index.js b/platform/i18n/src/locales/zh/index.js index 35a0a2f72bf..4e2517c1022 100644 --- a/platform/i18n/src/locales/zh/index.js +++ b/platform/i18n/src/locales/zh/index.js @@ -1,19 +1,45 @@ +import AboutModal from './AboutModal.json'; import Buttons from './Buttons.json'; import CineDialog from './CineDialog.json'; import Common from './Common.json'; +import DatePicker from './DatePicker.json'; import Header from './Header.json'; import MeasurementTable from './MeasurementTable.json'; import StudyList from './StudyList.json'; import UserPreferencesModal from './UserPreferencesModal.json'; +import ViewportDownloadForm from './ViewportDownloadForm.json'; +import StudyBrowser from './StudyBrowser.json'; +import SidePanel from './SidePanel.json'; +import Modes from './Modes.json'; +import PatientInfo from './PatientInfo.json'; +import Notification from './Notification.json'; +import ContextMenu from './ContextMenu.json'; +import Dialog from './Dialog.json'; +import Modals from './Modals.json'; +import Local from './Local.json'; +import ErrorBoundary from './ErrorBoundary.json'; export default { - zh: { + 'zh': { + AboutModal, Buttons, CineDialog, Common, + DatePicker, Header, MeasurementTable, StudyList, UserPreferencesModal, + ViewportDownloadForm, + StudyBrowser, + SidePanel, + Modes, + PatientInfo, + Notification, + ContextMenu, + Dialog, + Modals, + Local, + ErrorBoundary, }, -}; +}; \ No newline at end of file diff --git a/platform/i18n/src/utils.js b/platform/i18n/src/utils.js index 245013dac09..b3fbacbfe8d 100644 --- a/platform/i18n/src/utils.js +++ b/platform/i18n/src/utils.js @@ -49,6 +49,7 @@ const languagesMap = { te: 'Telugu', th: 'Thai', tr: 'Turkish', + 'tr-TR': 'Turkish (Turkey)', uk: 'Ukrainian', vi: 'Vietnamese', zh: 'Chinese', @@ -57,7 +58,7 @@ const languagesMap = { 'test-LNG': 'Test Language', }; -const getLanguageLabel = (language) => { +const getLanguageLabel = language => { return languagesMap[language]; }; diff --git a/platform/i18n/writeLocaleIndexFiles.js b/platform/i18n/writeLocaleIndexFiles.js index 9996c0efbd2..12e7c3ad628 100644 --- a/platform/i18n/writeLocaleIndexFiles.js +++ b/platform/i18n/writeLocaleIndexFiles.js @@ -21,7 +21,9 @@ const directories = getDirectories(directoryPath); function writeFile(filepath, name, content) { fs.writeFile(path.join(filepath, name), content, err => { - if (err) throw err; + if (err) { + throw err; + } }); } diff --git a/platform/ui/.storybook/main.ts b/platform/ui/.storybook/main.ts index 5bed58d84b1..54b817b1aea 100644 --- a/platform/ui/.storybook/main.ts +++ b/platform/ui/.storybook/main.ts @@ -1,12 +1,12 @@ -import path from 'path'; +import path, { dirname, join } from 'path'; import remarkGfm from 'remark-gfm'; import type { StorybookConfig } from '@storybook/react-webpack5'; const config: StorybookConfig = { stories: ['../src/**/*.stories.@(mdx)'], addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', + getAbsolutePath('@storybook/addon-links'), + getAbsolutePath('@storybook/addon-essentials'), // Other addons go here { name: '@storybook/addon-docs', @@ -19,9 +19,9 @@ const config: StorybookConfig = { }, }, ], - core: { builder: '@storybook/builder-webpack5' }, + core: {}, framework: { - name: '@storybook/react-webpack5', + name: getAbsolutePath('@storybook/react-webpack5'), options: {}, }, docs: { @@ -58,9 +58,7 @@ const config: StorybookConfig = { }); // Default rule for images /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/ - const fileLoaderRule = config.module.rules.find( - rule => rule.test && rule.test.test('.svg') - ); + const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg')); fileLoaderRule.exclude = /\.svg$/; config.module.rules.push({ @@ -93,3 +91,7 @@ const config: StorybookConfig = { }; export default config; + +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, 'package.json'))); +} diff --git a/platform/ui/.storybook/manager-head.html b/platform/ui/.storybook/manager-head.html index fa916fcbe84..9bca7f170a6 100644 --- a/platform/ui/.storybook/manager-head.html +++ b/platform/ui/.storybook/manager-head.html @@ -21,7 +21,9 @@ >