Compare commits

..
Author SHA1 Message Date
ohif-bot 1e4b78d78d chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.3
 - @ohif/extension-dicom-html@1.3.3
 - @ohif/extension-dicom-rt@0.6.6
 - @ohif/extension-dicom-segmentation@0.5.4
 - @ohif/extension-dicom-tag-browser@0.2.4
 - @ohif/extension-vtk@1.10.6
 - @ohif/core@2.12.5
 - @ohif/viewer@4.8.12
2021-03-03 18:31:51 +00:00
Igor Octaviano a56bdd035b Merge pull request #2290 from OHIF/bump-dcmjs
Update dependency: dcmjs@0.18.2
2021-03-03 15:27:54 -03:00
igoroctaviano 04901c296e Update dependency: dcmjs@0.18.2
dcmjs
  * @ohif/core: 0.18.1 → 0.18.2
  * @ohif/extension-cornerstone: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-html: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-rt: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-segmentation: 0.18.1 → 0.18.2
  * @ohif/extension-dicom-tag-browser: 0.18.1 → 0.18.2
  * @ohif/extension-vtk: 0.18.1 → 0.18.2
  * @ohif/viewer: 0.18.1 → 0.18.2
2021-03-03 14:35:06 -03:00
ohif-bot 59cadad98c chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.2
 - @ohif/extension-dicom-html@1.3.2
 - @ohif/extension-dicom-rt@0.6.5
 - @ohif/extension-dicom-segmentation@0.5.3
 - @ohif/extension-dicom-tag-browser@0.2.3
 - @ohif/extension-vtk@1.10.5
 - @ohif/core@2.12.4
 - @ohif/viewer@4.8.11
2021-02-25 21:31:47 +00:00
Igor Octaviano 64f87d2c93 Merge pull request #2281 from OHIF/fix/bump-dcmjs
Update dependency: dcmjs@0.18.1
2021-02-25 18:28:37 -03:00
igoroctaviano cfd66e5ef7 Update dependency: dcmjs@0.18.1
dcmjs
  * @ohif/core: 0.17.2 → 0.18.1
  * @ohif/extension-cornerstone: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-html: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-rt: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-segmentation: 0.17.2 → 0.18.1
  * @ohif/extension-dicom-tag-browser: 0.17.2 → 0.18.1
  * @ohif/extension-vtk: 0.17.2 → 0.18.1
  * @ohif/viewer: 0.17.2 → 0.18.1
2021-02-25 17:05:24 -03:00
Andrey Fedorovandigoroctaviano 6480389778 chore: add acknowledgment of IDC (#2255)
* add acknowldgment of IDC

resolves #1136

* Fix typo + remove IDC ack example

Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
2021-02-12 12:08:43 +01:00
ohif-bot 0e736e7a56 chore(release): publish [skip ci]
- @ohif/viewer@4.8.10
2021-02-08 11:56:46 +00:00
Igor Octaviano d246cd9842 Merge pull request #2267 from OHIF/IDC-2251
IDC-2251: Add error page and not found pages if failed to retrieve study data
2021-02-08 08:53:35 -03:00
igoroctaviano 4fa1eb683d Merge branch 'master' of github.com:OHIF/Viewers into IDC-2251 2021-02-07 22:29:45 -03:00
ohif-bot ea6842a276 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.2.2
 - @ohif/extension-vtk@1.10.4
 - @ohif/core@2.12.3
 - @ohif/ui@1.8.2
 - @ohif/viewer@4.8.9
2021-02-05 20:03:45 +00:00
igoroctaviano d97270f995 Merge branch 'master' of github.com:OHIF/Viewers into IDC-2251 2021-02-05 17:00:06 -03:00
Igor Octaviano 0ef76f8b66 Merge pull request #2260 from OHIF/IDC-2258
IDC-2258: Replace instance dropdown to slider for dicom tag browser
2021-02-05 16:59:38 -03:00
igoroctaviano 05b5a10af9 Merge branch 'master' of github.com:OHIF/Viewers into IDC-2258 2021-02-05 16:15:29 -03:00
ohif-bot e1150acaf6 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.1
 - @ohif/extension-dicom-html@1.3.1
 - @ohif/extension-dicom-rt@0.6.4
 - @ohif/extension-dicom-segmentation@0.5.2
 - @ohif/extension-dicom-tag-browser@0.2.1
 - @ohif/extension-vtk@1.10.3
 - @ohif/core@2.12.2
 - @ohif/viewer@4.8.8
2021-02-05 18:42:46 +00:00
Igor Octaviano 4abdbe0584 Merge branch 'master' into IDC-2258 2021-02-05 15:40:40 -03:00
Igor Octaviano 9223a52af9 Merge pull request #2268 from OHIF/IDC-2252
Update dependency: dcmjs@0.17.2 to fix Failure to load a valid SEG object due to incorrect expectations about ReferencedSegmentNumber
2021-02-05 15:40:09 -03:00
igoroctaviano 06f58d18bf Bump dcmjs 2021-02-03 17:38:20 -03:00
igoroctaviano 7c5f9c44b6 Add error page and not found pages if failed to retrieve study data 2021-02-03 13:58:46 -03:00
igoroctaviano ef341d1518 Replace instance dropdown to slider for dicom tag browser 2021-01-28 15:43:51 -03:00
ohif-bot d956963ef0 chore(release): publish [skip ci]
- @ohif/viewer@4.8.7
2021-01-28 14:32:43 +00:00
noerog 83b7d9e8b4 fix(config): Update Google Healthcare API endpoint from v1beta1 to v1 (#2256) 2021-01-28 15:29:53 +01:00
ohif-bot 12e73228d1 chore(release): publish [skip ci]
- @ohif/viewer@4.8.6
2021-01-26 12:43:46 +00:00
Jason Klotzer 4002401c66 fixes #2249 2021-01-26 13:39:48 +01:00
ohif-bot 18a8e059b6 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.3
 - @ohif/viewer@4.8.5
2021-01-21 17:33:28 +00:00
Igor Octaviano f3582e514e Merge pull request #2244 from OHIF/IDC-2123
feat(IDC-2123): When RTSTRUCT does not apply to the specific image series, the button and the panel should not be shown
2021-01-21 14:30:41 -03:00
igoroctaviano c76bfd4a58 Merge branch 'IDC-2123' of github.com:OHIF/Viewers into IDC-2123 2021-01-21 14:15:25 -03:00
igoroctaviano 7f8cf5f5da :wq
erge branch 'master' of github.com:OHIF/Viewers into IDC-2123
2021-01-21 14:15:13 -03:00
Igor Octaviano 5ef926477c Merge pull request #2240 from OHIF/IDC-1136
docs(IDC-1136): Add funding acknowledgment of the IDC project
2021-01-21 13:59:34 -03:00
igoroctaviano 87f5b97983 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1136 2021-01-21 13:12:00 -03:00
ohif-bot 4c7b2b4ae0 chore(release): publish [skip ci]
- @ohif/extension-dicom-microscopy@0.51.4
 - @ohif/viewer@4.8.4
2021-01-21 14:59:06 +00:00
igoroctaviano d1e52dd6a6 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1136 2021-01-21 11:55:17 -03:00
Igor Octaviano cb147dfc69 Merge pull request #2241 from OHIF/IDC-2223
feat(IDC-2223): Add error notification on microscopy viewport initialization errors
2021-01-21 11:54:51 -03:00
igoroctaviano a5fdeddfb4 Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223 2021-01-21 10:42:41 -03:00
ohif-bot e0e5f6000f chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.2
 - @ohif/extension-dicom-segmentation@0.5.1
 - @ohif/viewer@4.8.3
2021-01-21 13:36:40 +00:00
igoroctaviano cffb7d6f2f Merge branch 'master' of github.com:OHIF/Viewers into IDC-2223 2021-01-21 10:29:15 -03:00
Igor Octaviano 360616be13 Merge pull request #2243 from OHIF/IDC-1801
feat(IDC-1801): Improve/harmonize segmentations panel with rtstruct and add new visibility toggles
2021-01-21 10:28:40 -03:00
igoroctaviano d8fe8929b6 Merge branch 'IDC-1801' of github.com:OHIF/Viewers into IDC-1801 2021-01-21 09:37:50 -03:00
igoroctaviano 6b99a21227 Merge branch 'master' of github.com:OHIF/Viewers into IDC-1801 2021-01-21 09:37:39 -03:00
Igor Octaviano 16631e1fc4 Merge branch 'master' into IDC-1801 2021-01-21 09:36:51 -03:00
ohif-bot 587de94cea chore(release): publish [skip ci]
- @ohif/extension-vtk@1.10.2
 - @ohif/core@2.12.1
 - @ohif/ui@1.8.1
 - @ohif/viewer@4.8.2
2021-01-21 12:35:41 +00:00
Igor Octaviano 0ebe8a67d2 Merge branch 'master' into IDC-2123 2021-01-21 09:31:29 -03:00
Igor Octaviano 39a988dac7 Merge branch 'master' into IDC-1136 2021-01-21 09:31:26 -03:00
Igor Octaviano f95928bcdd Merge branch 'master' into IDC-2223 2021-01-21 09:31:22 -03:00
Igor Octaviano 0f736bd294 Merge branch 'master' into IDC-1801 2021-01-21 09:31:17 -03:00
Igor Octaviano 6cbd9839c6 Merge pull request #2246 from OHIF/IDC-2192
feat(IDC-2192): Notify buffer allocation failure trying to do MPR for a large series
2021-01-21 09:31:07 -03:00
igoroctaviano 649bc5825a Trigger netlify deploy 2021-01-21 09:06:29 -03:00
igoroctaviano 8038009afd Add exit mpr action 2021-01-20 20:59:03 -03:00
igoroctaviano f07f21117d Notify user of buffer errors from vtk / add possibility to add actions to notifications 2021-01-20 18:05:20 -03:00
igoroctaviano 77c7463c53 RTSTRUCT does not apply to the specific image series, the button and the panel should not be shown 2021-01-19 17:56:41 -03:00
igoroctaviano 449e8f25f6 Toggle visibility for cornerstone by default 2021-01-19 14:22:52 -03:00
igoroctaviano 437c01511e Toggle visibility for multiple labelmaps (overlapping) 2021-01-19 14:10:40 -03:00
igoroctaviano 00fddc1550 Improve/harmonize segmentations panel and add bulk visibility toggles 2021-01-16 01:19:12 -03:00
igoroctaviano 00b5cccae5 Add error message on microscopy viewport errors 2021-01-15 15:23:43 -03:00
igoroctaviano e545dd5b28 Add funding acknowledgment of the IDC project 2021-01-15 09:53:32 -03:00
ohif-bot efde6ed20e chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.6.1
 - @ohif/extension-vtk@1.10.1
 - @ohif/viewer@4.8.1
2020-12-10 16:49:23 +00:00
Igor Octaviano 32022f51f2 fix: panel issues and mpr button (IDC2122-IDC2117) 2020-12-10 17:44:44 +01:00
ohif-bot d282f72d75 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.0
 - @ohif/extension-dicom-html@1.3.0
 - @ohif/extension-dicom-rt@0.6.0
 - @ohif/extension-dicom-segmentation@0.5.0
 - @ohif/extension-dicom-tag-browser@0.2.0
 - @ohif/extension-vtk@1.10.0
 - @ohif/core@2.12.0
 - @ohif/ui@1.8.0
 - @ohif/viewer@4.8.0
2020-12-10 10:09:50 +00:00
Davide PunzoandIgor Octaviano 29fceacee9 feat: visualize overlapping segments in cornerstone (#2185)
* feat: visualize overlapping segments

load multiple labelMaps (splitted by dcmjs because overlapping) in a segmentations [IDC2164]

Dynamically update active labelmap [IDC2164]

Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
Co-authored-by: Igor Octaviano <igoroctaviano@gmail.com>
2020-12-10 11:04:27 +01:00
Igor Octaviano a7a5a359dc Use runtime values for version (#2204) 2020-12-10 09:56:30 +01:00
ohif-bot e362e895d4 chore(release): publish [skip ci]
- @ohif/viewer@4.7.2
2020-12-03 20:45:12 +00:00
Nikola Mijajlović 45a07dfa19 refactor: Update SidePanel.js to use a functional component (#2179) 2020-12-03 21:39:14 +01:00
ohif-bot 7323e63608 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.4
 - @ohif/viewer@4.7.1
2020-12-03 17:20:07 +00:00
Igor OctavianoandDavide Punzo 0baf18dcb2 fix: reset VOI on mpr reset (#2115)
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 18:11:14 +01:00
ohif-bot cca3d6bff2 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.5.0
 - @ohif/viewer@4.7.0
2020-12-03 16:47:50 +00:00
Igor OctavianoandDavide Punzo f3cecf129b feat: Update approach to better handle rtdose instead of disabling panel
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 17:36:02 +01:00
ohif-bot fe03fc6f55 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.3
 - @ohif/viewer@4.6.3
2020-12-03 16:11:30 +00:00
Davide Punzo b59187c74d Re #2113: disable 2D MPR when datasets have multiple slices for the same ImagePositionPatient (#2160)
fix: disable 2D MPR when datasets are 4D
2020-12-03 17:08:38 +01:00
ohif-bot 5210c80369 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.1.1
 - @ohif/extension-vtk@1.9.2
 - @ohif/core@2.11.1
 - @ohif/ui@1.7.1
 - @ohif/viewer@4.6.2
2020-12-03 15:19:00 +00:00
Igor OctavianoandDavide Punzo f57725ac8b fix: fallback to original dataset VR for XS
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 16:15:27 +01:00
ohif-bot 138e0fb3bf chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.1.0
 - @ohif/extension-vtk@1.9.1
 - @ohif/core@2.11.0
 - @ohif/ui@1.7.0
 - @ohif/viewer@4.6.1
2020-12-03 13:13:18 +00:00
be494a7376 feat: add show scroll option to modal
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 14:09:45 +01:00
ohif-bot ab23feefbb chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.0
 - @ohif/ui@1.6.0
 - @ohif/viewer@4.6.0
2020-12-03 12:29:32 +00:00
Igor OctavianoandDavide Punzo 4c079044f6 feat: Add error boundary and retry logic for network failures during dynamic imports (#2145)
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 13:23:43 +01:00
ohif-bot 81fcfc7370 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.0.5
2020-12-03 11:09:11 +00:00
Davide Punzo 6ec6446787 Merge pull request #2187 from OHIF/fix/tag-viewer-broken
Tag viewer broken for RTSTRUCT series
2020-12-03 11:59:58 +01:00
Davide Punzo 4b5a43641a Merge branch 'master' into fix/tag-viewer-broken 2020-12-03 11:39:23 +01:00
Erik Ziegler 2fb0b01fe2 ci: Fix release workflow issues with Gitbook-cli and missing executors (#2196) 2020-12-03 11:04:34 +01:00
ohif-bot 3632e5d495 chore(release): publish [skip ci]
- @ohif/viewer@4.5.29
2020-12-03 09:29:55 +00:00
Erik Ziegleranddannyrb dfe566e2aa ci: Use containerized PACS for running end-to-end tests #1122 (#1290)
* ci: Use containerized PACS for running end-to-end tests

* Try to fix cypress test results

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
2020-12-03 10:26:22 +01:00
Danny Brown 6c5ad9e98d change our definition of stale 2020-12-02 09:00:03 -05:00
igoroctaviano c0c3ab307d Add only valid sequences 2020-11-26 13:00:54 -03:00
100 changed files with 2869 additions and 1207 deletions

No files matched your search

+45 -22
View File
@@ -13,11 +13,23 @@ version: 2.1
##
orbs:
codecov: codecov/codecov@1.0.5
cypress: cypress-io/cypress@1.13.0
cypress: cypress-io/cypress@1.26.0
executors:
# Custom executor to override Cypress config
deploy-to-prod-executor:
docker:
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
environment:
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
chrome-and-pacs:
docker:
# Primary container image where all steps run.
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
- image: 'ohif/viewer-testdata:0.1-test'
defaults: &defaults
docker:
- image: circleci/node:12.9.1
- image: circleci/node:14.15.0
environment:
TERM: xterm # Enable colors in term
QUICK_BUILD: true
@@ -170,7 +182,7 @@ jobs:
DEPLOY_TO_DEV:
docker:
- image: circleci/node:12.9.1
- image: circleci/node:14.15.0
environment:
TERM: xterm
NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
@@ -185,7 +197,7 @@ jobs:
DEPLOY_TO_STAGING:
docker:
- image: circleci/node:12.9.1
- image: circleci/node:14.15.0
environment:
TERM: xterm
NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
@@ -200,7 +212,7 @@ jobs:
DEPLOY_TO_PRODUCTION:
docker:
- image: circleci/node:12.9.1
- image: circleci/node:14.15.0
environment:
TERM: xterm
NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0
@@ -257,7 +269,12 @@ jobs:
paths: .
DOCS_PUBLISH:
<<: *defaults
docker:
# Gitbook fails due to graceful-fs updates above this node version :-(
- image: circleci/node:12.9.1
environment:
TERM: xterm # Enable colors in term
working_directory: ~/repo
steps:
- checkout
- run:
@@ -317,16 +334,19 @@ workflows:
# E2E: PWA
- cypress/run:
name: 'E2E: PWA'
executor: cypress/browsers-chrome76
executor: chrome-and-pacs
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest
- run: |
# Clear yarn cache; update to latest
rm -rf ~/.yarn
npm i -g yarn
yarn -v
yarn: true
record: false
store_artifacts: false
record: true
store_artifacts: true
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build
start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
wait-on: 'http://localhost:3000'
@@ -337,21 +357,22 @@ workflows:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
- store_test_results:
path: platform/viewer/cypress/results
requires:
- UNIT_TESTS
# E2E: script-tag
- cypress/run:
name: 'E2E: Script Tag'
executor: cypress/browsers-chrome76
executor: chrome-and-pacs
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v' # Use yarn latest
yarn: true
record: false
store_artifacts: false
record: true
store_artifacts: true
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build:package
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build:package
start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
wait-on: 'http://localhost:3000'
@@ -362,6 +383,8 @@ workflows:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
- store_test_results:
path: platform/viewer/cypress/results
requires:
- UNIT_TESTS
@@ -372,7 +395,7 @@ workflows:
# Update hub.docker.org
- cypress/run:
name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76
executor: chrome-and-pacs
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
@@ -380,7 +403,7 @@ workflows:
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
build: 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" }}'
@@ -449,7 +472,7 @@ workflows:
# and record a Cypress dashboard test run
- cypress/run:
name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76
executor: chrome-and-pacs
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
@@ -457,7 +480,7 @@ workflows:
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
build: 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" }}'
+2 -2
View File
@@ -2,9 +2,9 @@
# https://github.com/apps/stale
#
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 21
daysUntilStale: 180
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
daysUntilClose: 9000
# Issues with these labels will never be considered stale
exemptLabels:
- 'Story :raised_hands:'
+4 -2
View File
@@ -3,7 +3,6 @@ const dotenv = require('dotenv');
//
const path = require('path');
const webpack = require('webpack');
const PACKAGE = require('../platform/viewer/package.json');
// ~~ RULES
const loadShadersRule = require('./rules/loadShaders.js');
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
@@ -77,7 +76,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
'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.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
'process.env.VERSION_NUMBER': webpack.DefinePlugin.runtimeValue(() => {
const package = require('../platform/viewer/package.json');
return JSON.stringify(package.version || '');
}, ['../platform/viewer/package.json']),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
+17 -2
View File
@@ -121,8 +121,7 @@ window.config = {
};
```
- Install the viewer:
`window.OHIFViewer.installViewer(window.config);`
- Install the viewer: `window.OHIFViewer.installViewer(window.config);`
This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
@@ -259,6 +258,22 @@ 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).
## Projects that contributed to OHIF Viewer
The following is a (partial) list of projects that contributed resources towards
development of OHIF Viewer:
- [NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/)
supported 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 the 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.
## License
MIT © [OHIF](https://github.com/OHIF)
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.2...@ohif/extension-cornerstone@2.10.3) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.1...@ohif/extension-cornerstone@2.10.2) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.0...@ohif/extension-cornerstone@2.10.1) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.6...@ohif/extension-cornerstone@2.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.9.6",
"version": "2.10.3",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -36,7 +36,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
+35
View File
@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.2...@ohif/extension-dicom-html@1.3.3) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.1...@ohif/extension-dicom-html@1.3.2) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.0...@ohif/extension-dicom-html@1.3.1) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-html
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.9...@ohif/extension-dicom-html@1.3.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.8...@ohif/extension-dicom-html@1.2.9) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "1.2.9",
"version": "1.3.3",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.3...@ohif/extension-dicom-microscopy@0.51.4) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.51.3",
"version": "0.51.4",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -62,11 +62,24 @@ class DicomMicroscopyViewport extends Component {
);
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
this.viewer = new microscopyViewer({
client: dicomWebClient,
metadata,
retrieveRendered: false,
});
try {
this.viewer = new microscopyViewer({
client: dicomWebClient,
metadata,
retrieveRendered: false,
});
} catch (error) {
console.error('[Microscopy Viewer] Failed to load:', error);
const { UINotificationService } = this.props.servicesManager.services;
if (UINotificationService) {
UINotificationService.show({
title: 'Microscopy Viewport',
message:
'Failed to load viewport. Please check that you have hardware acceleration enabled.',
type: 'error',
});
}
}
this.viewer.render({ container });
});
@@ -92,8 +105,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2>
) : (
<div style={style} ref={this.container} />
)}
<div style={style} ref={this.container} />
)}
</div>
);
}
+8 -10
View File
@@ -6,14 +6,6 @@ const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
});
const DicomMicroscopyViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
* Only required property. Should be a unique value across all extensions.
@@ -21,8 +13,14 @@ export default {
id: 'microscopy',
version,
getViewportModule() {
return DicomMicroscopyViewport;
getViewportModule({ servicesManager }) {
return props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} servicesManager={servicesManager} />
</React.Suspense>
);
};
},
getSopClassHandlerModule() {
return DicomMicroscopySopClassHandler;
+73
View File
@@ -3,6 +3,79 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.5...@ohif/extension-dicom-rt@0.6.6) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.4...@ohif/extension-dicom-rt@0.6.5) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.3...@ohif/extension-dicom-rt@0.6.4) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.2...@ohif/extension-dicom-rt@0.6.3) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.1...@ohif/extension-dicom-rt@0.6.2) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.0...@ohif/extension-dicom-rt@0.6.1) (2020-12-10)
### Bug Fixes
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
# [0.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.5.0...@ohif/extension-dicom-rt@0.6.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.9...@ohif/extension-dicom-rt@0.5.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [0.4.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.8...@ohif/extension-dicom-rt@0.4.9) (2020-10-12)
**Note:** Version bump only for package @ohif/extension-dicom-rt
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-rt",
"version": "0.4.9",
"version": "0.6.6",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF",
"license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"gl-matrix": "^3.3.0",
"prop-types": "^15.6.2",
"react": "^16.8.6",
@@ -15,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
id: 'OHIFDicomRTStructSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
getDisplaySetFromSeries: function (
series,
study,
dicomWebClient,
@@ -71,11 +71,11 @@ const OHIFDicomRTStructSopClassHandler = {
}
}
rtStructDisplaySet.getSourceDisplaySet = function(studies) {
rtStructDisplaySet.getSourceDisplaySet = function (studies) {
return getSourceDisplaySet(studies, rtStructDisplaySet);
};
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
return loadRTStruct(
rtStructDisplaySet,
referencedDisplaySet,
@@ -39,6 +39,8 @@
border-radius: 5px;
}
.icons .angle-double-down, .icons .angle-double-up {
.icons .angle-double-down,
.icons .angle-double-up {
margin-left: 15px;
margin-right: -5px;
}
@@ -9,16 +9,21 @@
.dcmrt-panel .dcmrt-panel-header {
display: flex;
flex-direction: row;
flex-direction: column;
justify-content: space-between;
color: white;
}
.dcmrt-panel .dcmrt-panel-header h3 {
margin-top: 0;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon {
align-self: flex-end;
color: var(--default-color);
cursor: pointer;
margin-bottom: 20px;
min-height: 23px;
max-height: 23px;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon:hover {
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
@@ -31,19 +31,20 @@ const refreshViewport = () => {
*
* @param {Object} props
* @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData
* @param {Array} props.getActiveViewport - get active viewport data
* @param {number} props.activeIndex - activeViewportIndex
* @param {number} props.isOpen - isOpen
* @returns component
*/
const RTPanel = ({
studies,
viewports,
activeIndex,
isOpen,
onContourItemClick,
activeContexts = [],
contexts = {},
activeViewport,
getActiveViewport,
}) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@@ -54,11 +55,11 @@ const RTPanel = ({
referencedDisplaysets: [],
sets: [],
selectedSet: null,
isLocked: false,
};
const [state, setState] = useState(DEFAULT_STATE);
const [showSettings, setShowSettings] = useState(false);
const activeViewport = viewports[activeIndex];
/*
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
@@ -78,6 +79,8 @@ const RTPanel = ({
* without subscribing to external events.
*/
useEffect(() => {
updateStructureSets();
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => {
@@ -88,20 +91,30 @@ const RTPanel = ({
};
}, []);
useEffect(() => {
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
setState(state => ({ ...state, isLocked: !referencedDisplaysets.length }));
}, [activeViewport]);
const updateStructureSets = () => {
const viewport = getActiveViewport();
const module = cornerstoneTools.getModule('rtstruct');
const StructureSets = module.state.StructureSets;
if (StructureSets && StructureSets.length) {
const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
viewport.SeriesInstanceUID
);
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const studyMetadata = studyMetadataManager.get(viewport.StudyInstanceUID);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
referencedSeriesInstanceUID: viewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
@@ -118,10 +131,6 @@ const RTPanel = ({
}
};
useEffect(() => {
updateStructureSets();
}, [studies, viewports, activeIndex]);
useEffect(() => {
setShowSettings(showSettings && !isOpen);
}, [isOpen]);
@@ -231,16 +240,19 @@ const RTPanel = ({
return (
<div className="dcmrt-panel">
<div className="dcmrt-panel-header">
{' '}
{!state.isLocked && (
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSettings(true)}
/>
)}
<h3>RT Structure Sets</h3>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSettings(true)}
/>
</div>
{!state.referencedDisplaysets.length && (
{!state.isLocked && !state.referencedDisplaysets.length && (
<LoadingIndicator expand height="70px" width="70px" />
)}
{state.sets &&
@@ -249,12 +261,15 @@ const RTPanel = ({
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
activeViewport.SeriesInstanceUID
);
const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
if (!loadedSet) return null;
return (
<PanelSection
key={SeriesInstanceUID}
@@ -276,17 +291,17 @@ const RTPanel = ({
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
}
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
activeViewport.SeriesInstanceUID
);
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(viewports[activeIndex], studies);
await displaySet.load(activeViewport, studies);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
activeViewport.SeriesInstanceUID
);
const selectedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
+27 -14
View File
@@ -5,6 +5,9 @@ import id from './id.js';
import RTPanel from './components/RTPanel/RTPanel';
import { version } from '../package.json';
import { utils } from '@ohif/core';
const { studyMetadataManager } = utils;
export default {
/**
* Only required property. Should be a unique value across all extensions.
@@ -45,24 +48,34 @@ export default {
icon: 'list',
label: 'RTSTRUCT',
target: 'rt-panel',
isDisabled: studies => {
isDisabled: (studies, activeViewport) => {
if (!studies) {
return true;
}
for (let i = 0; i < studies.length; i++) {
const study = studies[i];
if (study && study.series) {
for (let j = 0; j < study.series.length; j++) {
const series = study.series[j];
if (
/* Could be expanded to contain RTPLAN and RTDOSE information in the future */
['RTSTRUCT'].includes(series.Modality)
) {
return false;
}
}
if (activeViewport) {
const study = studies.find(
s => s.StudyInstanceUID === activeViewport.StudyInstanceUID
);
const ds = study.displaySets.find(
ds =>
ds.displaySetInstanceUID ===
activeViewport.displaySetInstanceUID
);
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaySets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
if (
referencedDisplaySets &&
referencedDisplaySets.some(ds =>
['RTSTRUCT'].includes(ds.Modality)
)
) {
return false;
}
}
+11 -6
View File
@@ -90,12 +90,17 @@ export default async function loadRTStruct(
const sopInstanceUID = ContourImageSequence
? ContourImageSequence.ReferencedSOPInstanceUID
: _getClosestSOPInstanceUID(
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
);
ContourData,
ContourGeometricType,
NumberOfContourPoints,
imageIdSopInstanceUidPairs
);
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
if (!imageId) {
continue;
}
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
toolState,
imageId,
@@ -294,7 +299,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
);
return imageIdSopInstanceUidPairsEntry.imageId;
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
};
function _getImageIdSopInstanceUidPairsForDisplaySet(
@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.3...@ohif/extension-dicom-segmentation@0.5.4) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.2...@ohif/extension-dicom-segmentation@0.5.3) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.1...@ohif/extension-dicom-segmentation@0.5.2) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.0...@ohif/extension-dicom-segmentation@0.5.1) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.6...@ohif/extension-dicom-segmentation@0.4.7) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.4.7",
"version": "0.5.4",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
@@ -24,7 +24,7 @@ const SegmentItem = ({
onClick,
itemClass,
color,
visible = true,
visible,
onVisibilityChange,
}) => {
const [isVisible, setIsVisible] = useState(visible);
@@ -33,6 +33,15 @@ const SegmentItem = ({
setIsVisible(visible);
}, [visible]);
const onClickHandler = () => onClick(index);
const onVisibilityChangeHandler = event => {
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility, index);
};
return (
<div className="dcmseg-segment-item">
<TableListItem
@@ -42,7 +51,7 @@ const SegmentItem = ({
itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section"
onItemClick={onClick}
onItemClick={onClickHandler}
>
<div>
<div className="segment-label" style={{ marginBottom: 4 }}>
@@ -63,12 +72,7 @@ const SegmentItem = ({
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={event => {
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
}}
onClick={onVisibilityChangeHandler}
/>
</div>
{false && <div className="segment-info">{'...'}</div>}
@@ -6,6 +6,7 @@
width: 100%;
display: flex;
flex-direction: column;
padding: 20px;
}
.dcmseg-segmentation-panel.disabled {
@@ -14,19 +15,19 @@
}
.dcmseg-segmentation-panel h3 {
padding-left: 20px;
margin-top: 0;
}
.dcmseg-segmentation-panel .segmentations {
padding: 0 20px 20px 20px;
padding-bottom: 10px;
}
.dcmseg-segmentation-panel .cog-icon {
margin: 20px 20px 0 0;
align-self: flex-end;
color: var(--default-color);
cursor: pointer;
min-height: 23px;
max-height: 23px;
}
.dcmseg-segmentation-panel .cog-icon:hover {
@@ -48,11 +49,11 @@
height: 150px;
}
.dcmseg-segmentation-panel .selector-form>div {
.dcmseg-segmentation-panel .selector-form > div {
width: 100%;
}
.dcmseg-segmentation-panel .selector-form>div:first-of-type {
.dcmseg-segmentation-panel .selector-form > div:first-of-type {
display: flex;
flex-direction: row;
justify-content: center;
@@ -64,3 +65,61 @@
flex: unset;
margin-right: 16px;
}
.SegmentsSection {
display: flex;
flex-direction: column;
color: var(--text-primary-color);
font-size: 12px;
margin-bottom: 0px;
height: 100%;
}
.SegmentsSection .header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: var(--ui-gray-dark);
border-radius: 5px;
font-weight: bold;
padding: 10px 15px;
}
.SegmentsSection .count {
font-size: 15px;
color: var(--active-color);
margin-left: 15px;
border-radius: 5px;
background-color: var(--ui-gray-darker);
text-align: center;
min-width: 25px;
}
.SegmentsSection .header .eye-icon,
.SegmentsSection .header .angle-double-down,
.SegmentsSection .header .angle-double-up {
cursor: pointer;
color: var(--active-color);
}
.SegmentsSection .header .eye-icon:hover,
.SegmentsSection .header .angle-double-down:hover,
.SegmentsSection .header .angle-double-up:hover {
color: var(--hover-color);
}
.SegmentsSection .header .eye-icon.expanded {
color: var(--default-color);
}
.SegmentsSection .header .icons {
display: flex;
align-items: center;
border-radius: 5px;
}
.SegmentsSection .header .icons .angle-double-down,
.SegmentsSection .header .icons .angle-double-up {
margin-left: 15px;
}
@@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import moment from 'moment';
import classNames from 'classnames';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
@@ -67,29 +68,104 @@ const SegmentationPanel = ({
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)',
selectedSegment: null,
selectedSegmentation: null,
showSegmentationSettings: false,
brushStackState: null,
labelmapList: [],
selectedSegment: 0,
selectedSegmentation: 0,
showSettings: false,
labelMapList: [],
segmentList: [],
cachedSegmentsProperties: [],
segmentsHidden: [],
segmentNumbers: [],
isLoading: false,
isDisabled: true,
});
const getActiveViewport = () => viewports[activeIndex];
const getFirstImageId = () => {
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
return studyMetadata.getFirstImageId(displaySetInstanceUID);
};
const getActiveLabelMaps3D = () => {
const { labelmaps3D, activeLabelmapIndex } = getBrushStackState();
return labelmaps3D[activeLabelmapIndex];
};
const getActiveLabelMapIndex = () => {
const { activeLabelmapIndex } = getBrushStackState();
return activeLabelmapIndex;
};
const getActiveSegmentIndex = () => {
const { activeSegmentIndex } = getActiveLabelMaps3D();
return activeSegmentIndex;
};
const getActiveLabelMaps2D = () => {
const { labelmaps2D } = getActiveLabelMaps3D();
return labelmaps2D;
};
const getCurrentDisplaySet = () => {
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const allDisplaySets = studyMetadata.getDisplaySets();
return allDisplaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
};
const setActiveSegment = segmentIndex => {
const activeSegmentIndex = getActiveSegmentIndex();
const activeViewport = getActiveViewport();
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const labelmap3D = getActiveLabelMaps3D();
labelmap3D.activeSegmentIndex = segmentIndex;
/**
* Activates the correct label map if clicked segment
* does not belong to the active labelmap
*/
const { StudyInstanceUID } = activeViewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const allDisplaySets = studyMetadata.getDisplaySets();
let selectedSegmentation;
let newLabelmapIndex = getActiveLabelMapIndex();
allDisplaySets.forEach(displaySet => {
if (displaySet.labelmapSegments) {
Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
if (
displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)
) {
newLabelmapIndex = labelmapIndex;
selectedSegmentation =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: labelmapIndex;
}
});
}
});
const brushStackState = getBrushStackState();
brushStackState.activeLabelmapIndex = newLabelmapIndex;
setState(state => ({ ...state, selectedSegmentation }));
refreshViewports();
return segmentIndex;
};
useEffect(() => {
const labelmapModifiedHandler = event => {
log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
updateState('brushStackState', module.state.series[firstImageId]);
refreshSegmentations();
};
/*
@@ -140,50 +216,38 @@ const SegmentationPanel = ({
};
}, [activeIndex, viewports]);
const refreshSegmentations = useCallback(() => {
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const refreshSegmentations = () => {
const activeViewport = getActiveViewport();
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
if (!isDisabled) {
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
const brushStackState = module.state.series[firstImageId];
const brushStackState = getBrushStackState();
if (brushStackState) {
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const labelmapList = getLabelmapList(
brushStackState,
firstImageId,
activeViewport
);
const segmentList = getSegmentList(
labelmap3D,
firstImageId,
brushStackState
);
const labelMapList = getLabelMapList();
const {
items: segmentList,
numbers: segmentNumbers,
segmentsHidden,
} = getSegmentList();
setState(state => ({
...state,
brushStackState,
selectedSegmentation: brushStackState.activeLabelmapIndex,
labelmapList,
segmentsHidden,
segmentNumbers,
labelMapList,
segmentList,
isDisabled,
}));
} else {
setState(state => ({
...state,
labelmapList: [],
segmentsHidden: [],
segmentNumbers: [],
labelMapList: [],
segmentList: [],
isDisabled,
}));
}
}
}, [viewports, activeIndex, state.isLoading]);
};
useEffect(() => {
refreshSegmentations();
@@ -200,306 +264,285 @@ const SegmentationPanel = ({
useEffect(() => {
setState(state => ({
...state,
showSegmentationSettings: state.showSegmentationSettings && !isOpen,
showSettings: state.showSettings && !isOpen,
}));
}, [isOpen]);
const getLabelmapList = useCallback(
(brushStackState, firstImageId, activeViewport) => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
const getLabelMapList = () => {
const activeViewport = getActiveViewport();
return referencedSegDisplaysets.map((displaySet, index) => {
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const isActiveLabelmap =
labelmapIndex === brushStackState.activeLabelmapIndex;
const displayDate = date.format('ddd, MMM Do YYYY');
const displayTime = date.format('h:mm:ss a');
const displayDescription = displaySet.SeriesDescription;
return referencedSegDisplaysets.map((displaySet, index) => {
const {
labelmapIndex,
originLabelMapIndex,
hasOverlapping,
SeriesDate,
SeriesTime,
} = displaySet;
return {
value: labelmapIndex,
title: displayDescription,
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await setActiveLabelmap(
activeViewport,
studies,
displaySet,
() => onSelectedSegmentationChange(),
onDisplaySetLoadFailure
);
updateState('selectedSegmentation', activatedLabelmapIndex);
},
};
});
},
[studies]
);
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const displayDate = date.format('ddd, MMM Do YYYY');
const displayDescription = displaySet.SeriesDescription;
const getSegmentList = useCallback(
(labelmap3D, firstImageId, brushStackState) => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
return {
value: hasOverlapping === true ? originLabelMapIndex : labelmapIndex,
title: displayDescription,
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await setActiveLabelmap(
activeViewport,
studies,
displaySet,
onSelectedSegmentationChange,
onDisplaySetLoadFailure
);
updateState('selectedSegment', sameSegment ? null : segmentNumber);
const validIndexList = [];
labelmap3D.labelmaps2D.forEach((labelMap2D, index) => {
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
validIndexList.push(index);
}
});
const avg = array => array.reduce((a, b) => a + b) / array.length;
const average = avg(validIndexList);
const closest = validIndexList.reduce((prev, curr) => {
return Math.abs(curr - average) < Math.abs(prev - average)
? curr
: prev;
});
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) {
return;
}
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(
element,
imageId,
segmentNumber
);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
}
if (isVTK()) {
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const allDisplaySets = studyMetadata.getDisplaySets();
const currentDisplaySet = allDisplaySets.find(
displaySet =>
displaySet.displaySetInstanceUID ===
activeViewport.displaySetInstanceUID
);
const frame = labelmap3D.labelmaps2D[closest];
onSegmentItemClick({
studies,
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
};
const isSegmentVisible = () => {
return !labelmap3D.segmentsHidden[segmentIndex];
};
const toggleSegmentVisibility = () => {
const segmentsHidden = labelmap3D.segmentsHidden;
segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
return !segmentsHidden[segmentIndex];
};
const cachedSegmentProperties =
state.cachedSegmentsProperties[segmentNumber];
let visible = isSegmentVisible();
if (
cachedSegmentProperties &&
cachedSegmentProperties.visible !== visible
) {
toggleSegmentVisibility();
}
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={() => setCurrentSelectedSegment()}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={visible}
onVisibilityChange={newVisibility => {
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
module.setters.toggleSegmentVisibility(
element,
segmentNumber,
brushStackState.activeLabelmapIndex
);
}
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, newVisibility);
}
updateCachedSegmentsProperties(segmentNumber, {
visible: newVisibility,
});
refreshViewports();
}}
/>
);
}
return segmentList;
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
},
[activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
);
const updateCachedSegmentsProperties = (segmentNumber, properties) => {
const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] = segmentProperties
? { ...segmentProperties, ...properties }
: properties;
updateState('cachedSegmentsProperties', segmentsProperties);
setState(state => ({
...state,
selectedSegmentation: activatedLabelmapIndex,
}));
},
};
});
};
useEffect(() => {
updateState('cachedSegmentsProperties', []);
}, [activeContexts]);
const setCurrentSelectedSegment = segmentNumber => {
setActiveSegment(segmentNumber);
const updateState = (field, value) => {
setState(state => ({ ...state, [field]: value }));
const sameSegment = state.selectedSegment === segmentNumber;
if (!sameSegment) {
setState(state => ({ ...state, selectedSegment: segmentNumber }));
}
const validIndexList = [];
getActiveLabelMaps2D().forEach((labelMap2D, index) => {
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
validIndexList.push(index);
}
});
const avg = array => array.reduce((a, b) => a + b) / array.length;
const average = avg(validIndexList);
const closest = validIndexList.reduce((prev, curr) => {
return Math.abs(curr - average) < Math.abs(prev - average) ? curr : prev;
});
if (isCornerstone()) {
const element = getEnabledElement();
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) return;
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(element, imageId, segmentNumber);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
}
if (isVTK()) {
const labelMaps3D = getActiveLabelMaps3D();
const currentDisplaySet = getCurrentDisplaySet();
const frame = labelMaps3D.labelmaps2D[closest];
onSegmentItemClick({
studies,
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
SOPClassUID: getActiveViewport().sopClassUIDs[0],
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
};
const getColorLUTTable = () => {
const { state } = cornerstoneTools.getModule('segmentation');
const { colorLUTIndex } = getActiveLabelMaps3D();
return state.colorLutTables[colorLUTIndex];
};
const getEnabledElement = () => {
const enabledElements = cornerstone.getEnabledElements();
return enabledElements[activeIndex].element;
};
const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => {
/** Get all labelmaps with this segmentNumber (overlapping segments) */
const { labelmaps3D } = getBrushStackState();
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
segmentsOnLabelmap.includes(segmentNumber)
);
});
let segmentsHidden = [];
possibleLabelMaps3D.forEach(labelmap3D => {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
];
});
setState(state => ({ ...state, segmentsHidden }));
refreshSegmentations();
refreshViewports();
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, isVisible);
}
};
const getSegmentList = () => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = getActiveLabelMaps2D()
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const labelmap3D = getActiveLabelMaps3D();
const colorLutTable = getColorLUTTable();
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
const segmentNumbers = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
const sameSegment = state.selectedSegment === segmentNumber;
segmentNumbers.push(segmentNumber);
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={!labelmap3D.segmentsHidden[segmentIndex]}
onVisibilityChange={onSegmentVisibilityChangeHandler}
/>
);
}
return {
items: segmentList,
numbers: segmentNumbers,
segmentsHidden: labelmap3D.segmentsHidden,
};
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
};
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
updateState('brushRadius', updatedRadius);
setState(state => ({ ...state, brushRadius: updatedRadius }));
const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius);
}
};
const decrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault();
if (labelmap3D.activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--;
if (activeSegmentIndex > 1) {
activeSegmentIndex--;
}
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor();
};
const incrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault();
labelmap3D.activeSegmentIndex++;
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
activeSegmentIndex++;
setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
updateState('brushColor', color);
setState(state => ({ ...state, brushColor: color }));
};
const getBrushStackState = () => {
const module = cornerstoneTools.getModule('segmentation');
const firstImageId = getFirstImageId();
const brushStackState = module.state.series[firstImageId];
return brushStackState;
};
const getActiveSegmentColor = () => {
if (!state.brushStackState) {
const brushStackState = getBrushStackState();
if (!brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
const colorLutTable = getColorLUTTable();
const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`;
};
@@ -517,65 +560,103 @@ const SegmentationPanel = ({
refreshViewports();
};
const onVisibilityChangeHandler = isVisible => {
let segmentsHidden = [];
state.segmentNumbers.forEach(segmentNumber => {
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, isVisible);
}
/** Get all labelmaps with this segmentNumber (overlapping segments) */
const { labelmaps3D } = getBrushStackState();
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
segmentsOnLabelmap.includes(segmentNumber)
);
});
possibleLabelMaps3D.forEach(labelmap3D => {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
];
});
});
setState(state => ({ ...state, segmentsHidden }));
refreshSegmentations();
refreshViewports();
};
const disabledConfigurationFields = [
'outlineAlpha',
'shouldRenderInactiveLabelmaps',
];
if (state.showSegmentationSettings) {
const selectedSegmentationOption = state.labelMapList.find(
i => i.value === state.selectedSegmentation
);
if (state.showSettings) {
return (
<SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration}
onBack={() => updateState('showSegmentationSettings', false)}
onBack={() => setState(state => ({ ...state, showSettings: false }))}
onChange={updateConfiguration}
/>
);
} else {
return (
<div
className={`dcmseg-segmentation-panel ${state.isDisabled &&
'disabled'}`}
className={classNames('dcmseg-segmentation-panel', {
disabled: state.isDisabled,
})}
>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => updateState('showSegmentationSettings', true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
defaultColor={state.brushColor}
index={state.selectedSegment}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
<BrushRadius
value={brushRadius}
value={state.brushRadius}
onChange={updateBrushSize}
min={configuration.minRadius}
max={configuration.maxRadius}
/>
</form>
)}
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setState(state => ({ ...state, showSettings: true }))}
/>
<h3>Segmentations</h3>
<div className="segmentations">
<SegmentationSelect
value={state.labelmapList.find(
i => i.value === state.selectedSegmentation
)}
value={selectedSegmentationOption}
formatOptionLabel={SegmentationItem}
options={state.labelmapList}
options={state.labelMapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={state.segmentList.length} />}
>
{state.segmentList}
</TableList>
</ScrollableArea>
<SegmentsSection
count={state.segmentList.length}
isVisible={
state.segmentsHidden.filter(isHidden => isHidden === true).length <
state.segmentNumbers.length
}
onVisibilityChange={onVisibilityChangeHandler}
>
<ScrollableArea>
<TableList headless>{state.segmentList}</TableList>
</ScrollableArea>
</SegmentsSection>
</div>
);
}
@@ -610,7 +691,7 @@ SegmentationPanel.propTypes = {
SegmentationPanel.defaultProps = {};
/**
* Returns SEG Displaysets that reference the target series, sorted by dateTime
* Returns SEG DisplaySets that reference the target series, sorted by dateTime
*
* @param {string} StudyInstanceUID
* @param {string} SeriesInstanceUID
@@ -634,37 +715,48 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets;
};
/**
*
* @param {*} firstImageId
* @param {*} activeSegmentIndex
* @returns
*/
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const SegmentsSection = ({
count,
children,
isVisible: defaultVisibility,
onVisibilityChange,
}) => {
const [isVisible, setIsVisible] = useState(defaultVisibility);
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
const onVisibilityChangeHandler = () => {
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
};
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmap3D.activeSegmentIndex = segmentIndex;
useEffect(() => {
setIsVisible(defaultVisibility);
}, [defaultVisibility]);
refreshViewports();
return segmentIndex;
};
const SegmentsHeader = ({ count }) => {
return (
<React.Fragment>
<div className="tableListHeaderTitle">Segments</div>
<div className="numberOfItems">{count}</div>
</React.Fragment>
<div className="SegmentsSection">
<div className="header">
<div>Segments</div>
<div className="icons">
<Icon
className={`eye-icon ${isVisible && 'expanded'}`}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={onVisibilityChangeHandler}
/>
<div className="count">{count}</div>
</div>
</div>
{children}
</div>
);
};
const noop = () => {};
SegmentsSection.defaultProps = {
onVisibilityChange: noop,
};
export default SegmentationPanel;
@@ -1,6 +1,11 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
import getSourceDisplaySet from './getSourceDisplaySet';
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
const { DicomLoaderService } = OHIF.utils;
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
@@ -46,11 +51,11 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
hasOverlapping: false,
SeriesDate,
SeriesTime,
SeriesNumber,
@@ -62,11 +67,110 @@ export default function getSopClassHandlerModule({ servicesManager }) {
return getSourceDisplaySet(studies, segDisplaySet);
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
segDisplaySet.load = async function(referencedDisplaySet, studies) {
segDisplaySet.isLoaded = true;
const { StudyInstanceUID } = referencedDisplaySet;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
segDisplaySet,
studies
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
const imageIds = _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
return new Promise(async (resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const {
labelmapBufferArray,
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray,
} = results;
let labelmapIndex;
if (labelmapBufferArray.length > 1) {
let labelmapIndexes = [];
for (let i = 0; i < labelmapBufferArray.length; ++i) {
labelmapIndexes.push(
await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[i],
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray[i]
)
);
}
/**
* Since overlapping segmentations have virtual labelmaps,
* originLabelMapIndex is used in the panel to select the correct dropdown value.
*/
segDisplaySet.hasOverlapping = true;
segDisplaySet.originLabelMapIndex = labelmapIndexes[0];
labelmapIndex = labelmapIndexes[0];
console.warn('Overlapping segmentations!');
} else {
labelmapIndex = await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[0],
segMetadata,
segmentsOnFrame,
[]
);
}
resolve(labelmapIndex);
});
};
return segDisplaySet;
},
};
}
function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(displaySet => {
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => image.getImageId());
}
@@ -1,92 +1,68 @@
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { DicomLoaderService } = OHIF.utils;
export default async function loadSegmentation(
imageIds,
segDisplaySet,
referencedDisplaySet,
studies
labelmapBuffer,
segMetadata,
segmentsOnFrame,
labelmapSegments
) {
const { StudyInstanceUID } = referencedDisplaySet;
const { setters } = cornerstoneTools.getModule('segmentation');
// Set here is loading is asynchronous.
// If this function throws its set back to false.
segDisplaySet.isLoaded = true;
// TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
segDisplaySet,
studies
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
);
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
if (!segDisplaySet.labelmapSegments) {
segDisplaySet.labelmapSegments = {};
}
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
/**
* Cache each labelmap segments.
* This data is used to determine the active label map when a given segment is activated/clicked.
*/
segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length
? Array.from(
new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))
)
: [];
segDisplaySet.labelmapIndex = labelmapIndex;
const imageIds = _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
return new Promise((resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
const { setters } = cornerstoneTools.getModule('segmentation');
// TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
);
segDisplaySet.labelmapIndex = labelmapIndex;
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
resolve(labelmapIndex);
});
return labelmapIndex;
}
function _getNextLabelmapIndex(firstImageId) {
@@ -172,38 +148,3 @@ function _getNextColorLUTIndex() {
return colorLUTIndex;
}
function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(displaySet => {
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => image.getImageId());
}
@@ -37,9 +37,14 @@ export default async function setActiveLabelmap(
? brushStackState.activeLabelmapIndex
: undefined;
if (displaySet.labelmapIndex === activeLabelmapIndex) {
let labelmapIndex =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: displaySet.labelmapIndex;
if (labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return displaySet.labelmapIndex;
return labelmapIndex;
}
if (!displaySet.isLoaded) {
@@ -55,13 +60,18 @@ export default async function setActiveLabelmap(
await loadPromise;
}
labelmapIndex =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: displaySet.labelmapIndex;
// This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
brushStackState.activeLabelmapIndex = labelmapIndex;
refreshViewports();
callback();
return displaySet.labelmapIndex;
return labelmapIndex;
}
+73
View File
@@ -3,6 +3,79 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.3...@ohif/extension-dicom-tag-browser@0.2.4) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.2...@ohif/extension-dicom-tag-browser@0.2.3) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.1...@ohif/extension-dicom-tag-browser@0.2.2) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.0...@ohif/extension-dicom-tag-browser@0.2.1) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.1...@ohif/extension-dicom-tag-browser@0.2.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.0...@ohif/extension-dicom-tag-browser@0.1.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03)
### Features
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
## [0.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.4...@ohif/extension-dicom-tag-browser@0.0.5) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.3...@ohif/extension-dicom-tag-browser@0.0.4) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-tag-browser",
"version": "0.0.4",
"version": "0.2.4",
"description": "OHIF extension for checking DICOM headers.",
"author": "OHIF",
"license": "MIT",
@@ -28,7 +28,7 @@
},
"peerDependencies": {
"@ohif/core": "^2.6.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"react": "^16.8.6"
},
"dependencies": {
@@ -3,6 +3,11 @@
margin-left: auto;
}
.dicom-tag-browser-table-wrapper {
height: 500px;
overflow-y: scroll;
}
.dicom-tag-browser-table tr {
padding-left: 10px;
padding-right: 10px;
@@ -11,6 +16,20 @@
white-space: nowrap;
}
.dicom-tag-browser-content {
overflow: hidden;
width: 100%;
padding-bottom: 50px;
}
.dicom-tag-browser-instance-range .range {
height: 20px;
}
.dicom-tag-browser-instance-range {
padding: 20px 0 20px 0;
}
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
text-align: center;
}
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { classes } from '@ohif/core';
import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
import { Range } from '@ohif/ui';
import dcmjs from 'dcmjs';
import DicomBrowserSelect from './DicomBrowserSelect';
import moment from 'moment';
@@ -10,6 +11,8 @@ const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary;
const { metadataProvider } = OHIFCornerstone;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const [
activeDisplaySetInstanceUID,
@@ -17,6 +20,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(0);
const [tags, setTags] = useState([]);
const [meta, setMeta] = useState('');
const [instanceList, setInstanceList] = useState([]);
const [displaySetList, setDisplaySetList] = useState([]);
const [isImageStack, setIsImageStack] = useState(false);
@@ -83,6 +87,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
}
setTags(getSortedTags(metadata));
setMeta(metadata);
setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack);
@@ -95,47 +100,56 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
let instanceSelectList = null;
if (isImageStack) {
const selectedInstanceValue = instanceList[activeInstance];
instanceSelectList = (
<DicomBrowserSelect
value={selectedInstanceValue}
formatOptionLabel={DicomBrowserSelectItem}
options={instanceList}
/>
<div className="dicom-tag-browser-instance-range">
<Range
showValue
step={1}
min={1}
max={instanceList.length - 1}
value={activeInstance}
valueRenderer={value => <p>Instance Number: {value}</p>}
onChange={({ target }) => {
const instanceIndex = parseInt(target.value);
setActiveInstance(instanceIndex);
}}
/>
</div>
);
}
return (
<div>
<div className="dicom-tag-browser-content">
<DicomBrowserSelect
value={selectedDisplaySetValue}
formatOptionLabel={DicomBrowserSelectItem}
options={displaySetList}
/>
{instanceSelectList}
<DicomTagTable tags={tags}></DicomTagTable>
<div className="dicom-tag-browser-table-wrapper">
<DicomTagTable tags={tags} meta={meta}></DicomTagTable>
</div>
</div>
);
};
function DicomTagTable({ tags }) {
const rows = getFormattedRowsFromTags(tags);
function DicomTagTable({ tags, meta }) {
const rows = getFormattedRowsFromTags(tags, meta);
return (
<div>
<table className="dicom-tag-browser-table">
<table className="dicom-tag-browser-table">
<tbody>
<tr>
<th className="dicom-tag-browser-table-left">Tag</th>
<th className="dicom-tag-browser-table-left">Value Representation</th>
<th className="dicom-tag-browser-table-left">Keyword</th>
<th className="dicom-tag-browser-table-left">Value</th>
</tr>
{rows.map(row => {
{rows.map((row, index) => {
const className = row.className ? row.className : null;
return (
<tr className={className}>
<tr className={className} key={`DICOMTagRow-${index}`}>
<td>{row[0]}</td>
<td className="dicom-tag-browser-table-center">{row[1]}</td>
<td>{row[2]}</td>
@@ -143,12 +157,12 @@ function DicomTagTable({ tags }) {
</tr>
);
})}
</table>
</div>
</tbody>
</table>
);
}
function getFormattedRowsFromTags(tags) {
function getFormattedRowsFromTags(tags, meta) {
const rows = [];
tags.forEach(tagInfo => {
@@ -175,6 +189,21 @@ function getFormattedRowsFromTags(tags) {
rows.push(...formatedRowsFromTags);
});
} else {
if (tagInfo.vr === 'xs') {
try {
const dataset = metadataProvider.getStudyDataset(
meta.StudyInstanceUID
);
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
const originalTagInfo = dataset[tag];
tagInfo.vr = originalTagInfo.vr;
} catch (error) {
console.error(
`Failed to parse value representation for tag '${tagInfo.keyword}'`
);
}
}
rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr,
@@ -247,10 +276,11 @@ function getRows(metadata, depth = 0) {
sequenceAsArray.forEach(item => {
const sequenceRows = getRows(item, depth + 1);
// Sort the sequence group.
_sortTagList(sequenceRows);
sequence.values.push(sequenceRows);
if (sequenceRows.length) {
// Sort the sequence group.
_sortTagList(sequenceRows);
sequence.values.push(sequenceRows);
}
});
continue;
@@ -276,13 +306,12 @@ function getRows(metadata, depth = 0) {
} else if (value.Alphabetic) {
value = value.Alphabetic;
} else {
console.error('Unrecognised Value for element:');
console.error(value);
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
console.warn(value);
value = ' ';
}
} else {
console.error('Unrecognised Value for element:');
console.error(value);
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
value = ' ';
}
}
@@ -34,6 +34,7 @@ export default function getCommandsModule(servicesManager) {
content: WrappedDicomTagBrowser,
title: `DICOM Tag Browser`,
fullscreen: true,
noScroll: true,
});
},
};
+108
View File
@@ -3,6 +3,114 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.5...@ohif/extension-vtk@1.10.6) (2021-03-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.4...@ohif/extension-vtk@1.10.5) (2021-02-25)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.3...@ohif/extension-vtk@1.10.4) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.2...@ohif/extension-vtk@1.10.3) (2021-02-05)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.1...@ohif/extension-vtk@1.10.2) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.0...@ohif/extension-vtk@1.10.1) (2020-12-10)
### Bug Fixes
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.4...@ohif/extension-vtk@1.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.3...@ohif/extension-vtk@1.9.4) (2020-12-03)
### Bug Fixes
* reset VOI on mpr reset ([#2115](https://github.com/OHIF/Viewers/issues/2115)) ([0baf18d](https://github.com/OHIF/Viewers/commit/0baf18dcb2985378016236b735329346a851e14f))
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.2...@ohif/extension-vtk@1.9.3) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.1...@ohif/extension-vtk@1.9.2) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.8.0...@ohif/extension-vtk@1.9.0) (2020-12-03)
### Features
* Add error boundary and retry logic for network failures during dynamic imports ([#2145](https://github.com/OHIF/Viewers/issues/2145)) ([4c07904](https://github.com/OHIF/Viewers/commit/4c079044f6ae2381c6054d8d77414100152d1d19))
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.16...@ohif/extension-vtk@1.8.0) (2020-11-30)
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.8.0",
"version": "1.10.6",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -35,7 +35,7 @@
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2"
},
"devDependencies": {
"@ohif/core": "^2.10.7",
"@ohif/ui": "^1.5.7",
"@ohif/core": "^2.12.5",
"@ohif/ui": "^1.8.2",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3",
+81 -40
View File
@@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component {
SOPInstanceUID,
frameIndex
) => {
const { UINotificationService } = this.props.servicesManager.services;
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
StudyInstanceUID,
@@ -159,6 +161,18 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
if (
brushStackState.labelmaps3D.length > 1 &&
this.props.viewportIndex === 0
) {
UINotificationService.show({
title: 'Overlapping Segmentation Found',
message:
'Overlapping segmentations cannot be displayed when in MPR mode',
type: 'info',
});
}
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
isHidden => {
return { visible: !isHidden };
@@ -296,52 +310,79 @@ class OHIFVTKViewport extends Component {
seriesDescription: displaySet.seriesDescription,
};
const {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
} = this.getViewportData(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPInstanceUID,
frameIndex
);
try {
const {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
} = this.getViewportData(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPInstanceUID,
frameIndex
);
this.imageDataObject = imageDataObject;
this.imageDataObject = imageDataObject;
/* TODO: Not currently used until we have drawing tools in vtkjs.
if (!labelmap) {
labelmap = createLabelMapImageData(data);
} */
/* TODO: Not currently used until we have drawing tools in vtkjs.
if (!labelmap) {
labelmap = createLabelMapImageData(data);
} */
const volumeActor = this.getOrCreateVolume(
imageDataObject,
displaySetInstanceUID
);
const volumeActor = this.getOrCreateVolume(
imageDataObject,
displaySetInstanceUID
);
this.setState(
{
percentComplete: 0,
dataDetails,
},
() => {
this.loadProgressively(imageDataObject);
this.setState(
{
percentComplete: 0,
dataDetails,
},
() => {
this.loadProgressively(imageDataObject);
// TODO: There must be a better way to do this.
// We do this so that if all the data is available the react-vtkjs-viewport
// Will render _something_ before the volumes are set and the volume
// Construction that happens in react-vtkjs-viewport locks up the CPU.
setTimeout(() => {
this.setState({
volumes: [volumeActor],
paintFilterLabelMapImageData: labelmapDataObject,
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
labelmapColorLUT,
});
}, 200);
// TODO: There must be a better way to do this.
// We do this so that if all the data is available the react-vtkjs-viewport
// Will render _something_ before the volumes are set and the volume
// Construction that happens in react-vtkjs-viewport locks up the CPU.
setTimeout(() => {
this.setState({
volumes: [volumeActor],
paintFilterLabelMapImageData: labelmapDataObject,
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
labelmapColorLUT,
});
}, 200);
}
);
} catch (error) {
const errorTitle = 'Failed to load 2D MPR';
console.error(errorTitle, error);
const { UINotificationService } = this.props.servicesManager.services;
if (this.props.viewportIndex === 0) {
const message = error.message.includes('buffer')
? 'Dataset is too big to display in MPR'
: error.message;
console.error(errorTitle, error);
UINotificationService.show({
title: errorTitle,
message,
type: 'error',
autoClose: false,
action: {
label: 'Exit 2D MPR',
onClick: ({ close }) => {
// context: 'ACTIVE_VIEWPORT::VTK',
close();
this.props.commandsManager.runCommand('setCornerstoneLayout');
},
},
});
}
);
this.setState({ isLoaded: true });
}
}
componentDidMount() {
-37
View File
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+8 -4
View File
@@ -17,6 +17,7 @@ const { BlendMode } = Constants;
const commandsModule = ({ commandsManager, UINotificationService }) => {
// TODO: Put this somewhere else
let apis = {};
let defaultVOI;
async function _getActiveViewportVTKApi(viewports) {
const {
@@ -119,9 +120,11 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
return apis[index];
},
resetMPRView() {
apis.forEach(api => {
api.resetOrientation();
});
// Reset orientation
apis.forEach(api => api.resetOrientation());
// Reset VOI
if (defaultVOI) setVOI(defaultVOI);
// Reset the crosshairs
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
@@ -172,7 +175,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
segmentNumber,
frameIndex,
frame,
done = () => {},
done = () => { },
}) => {
let api = apis[viewports.activeViewportIndex];
@@ -387,6 +390,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
// Get current VOI if cornerstone viewport.
const cornerstoneVOI = getVOIFromCornerstoneViewport();
defaultVOI = cornerstoneVOI;
const viewportProps = [
{
+10 -3
View File
@@ -1,5 +1,6 @@
import React from 'react';
import asyncComponent from './asyncComponent.js';
import { asyncComponent, retryImport } from '@ohif/ui';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import withCommandsManager from './withCommandsManager.js';
@@ -8,7 +9,9 @@ import { version } from '../package.json';
// import loadLocales from './loadLocales';
const OHIFVTKViewport = asyncComponent(() =>
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
retryImport(() =>
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
)
);
const vtkExtension = {
@@ -20,7 +23,11 @@ const vtkExtension = {
getViewportModule({ commandsManager, servicesManager }) {
const ExtendedVTKViewport = props => (
<OHIFVTKViewport {...props} servicesManager={servicesManager} />
<OHIFVTKViewport
{...props}
servicesManager={servicesManager}
commandsManager={commandsManager}
/>
);
return withCommandsManager(ExtendedVTKViewport, commandsManager);
},
@@ -31,7 +31,63 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
if (!displaySet) {
return false;
};
}
// 2D MPR is not currently available for 4D datasets.
// Assuming that slices at different time have the same position, here we just check if
// there are multiple slices for the same ImagePositionPatient and disable MPR.
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
for (let ii = 0; ii < displaySet.numImageFrames; ++ii) {
const image = displaySet.images[ii];
if (!image) continue;
const imageIdControl = image.getImageId()
const instanceMetadataControl = cornerstone.metaData.get('instance', imageIdControl)
if (!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[0];
let yImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[1];
let zImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[2];
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj) {
const image = displaySet.images[jj];
if (!image) continue;
const imageId = image.getImageId()
const instanceMetadata = cornerstone.metaData.get('instance', imageId)
if (!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
if (xImagePositionPatientControl === xImagePositionPatient &&
yImagePositionPatientControl === yImagePositionPatient &&
zImagePositionPatientControl === zImagePositionPatient) {
return false;
}
}
}
return displaySet.isReconstructable;
};
+1
View File
@@ -28,6 +28,7 @@
"test:unit": "jest --collectCoverage",
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
"test:e2e": "lerna run test:e2e --stream",
"test:e2e:script-tag": "lerna run test:e2e:script-tag --stream",
"test:e2e:ci": "lerna run test:e2e:ci --stream",
"test:e2e:dist": "lerna run test:e2e:dist --stream",
"test:e2e:serve": "lerna run test:e2e:serve --stream",
+73
View File
@@ -3,6 +3,79 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.12.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.4...@ohif/core@2.12.5) (2021-03-03)
**Note:** Version bump only for package @ohif/core
## [2.12.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.3...@ohif/core@2.12.4) (2021-02-25)
**Note:** Version bump only for package @ohif/core
## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.2...@ohif/core@2.12.3) (2021-02-05)
**Note:** Version bump only for package @ohif/core
## [2.12.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.1...@ohif/core@2.12.2) (2021-02-05)
**Note:** Version bump only for package @ohif/core
## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.0...@ohif/core@2.12.1) (2021-01-21)
**Note:** Version bump only for package @ohif/core
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.11.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.0...@ohif/core@2.11.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03)
### Features
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
## [2.10.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.6...@ohif/core@2.10.7) (2020-10-07)
**Note:** Version bump only for package @ohif/core
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.10.7",
"version": "2.12.5",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -39,7 +39,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
@@ -20,6 +20,7 @@ class MetadataProvider {
writable: false,
value: new Map(),
});
this.datasets = {};
}
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
@@ -52,6 +53,7 @@ class MetadataProvider {
SOPInstanceUID,
} = naturalizedDataset;
this._getAndCacheStudyDataset(StudyInstanceUID, dicomJSONDataset);
const study = this._getAndCacheStudy(StudyInstanceUID);
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
@@ -71,6 +73,16 @@ class MetadataProvider {
this.imageIdToUIDs.set(imageId, uids);
}
_getAndCacheStudyDataset(StudyInstanceUID, dataset) {
if (!this.datasets[StudyInstanceUID]) {
this.datasets[StudyInstanceUID] = dataset;
}
}
getStudyDataset(StudyInstanceUID) {
return this.datasets[StudyInstanceUID];
}
_getAndCacheStudy(StudyInstanceUID) {
const studies = this.studies;
@@ -83,6 +95,7 @@ class MetadataProvider {
return study;
}
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
let series = study.series.get(SeriesInstanceUID);
@@ -9,6 +9,8 @@
* @property {boolean} [closeButton=true] Should the modal body render the close button.
* @property {string} [title=null] Should the modal render the title independently of the body content.
* @property {string} [customClassName=null] The custom class to style the modal.
* @property {boolean} [showScrollbar=false] Show or hide scrollbar.
* @property {boolean} [noScroll=false] Disable or not the scrollbar.
*/
const name = 'UIModalService';
@@ -28,7 +30,7 @@ const serviceImplementation = {
/**
* Show a new UI modal;
*
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName }
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName, showScrollbar, noScroll }
*/
function _show({
content = null,
@@ -40,6 +42,8 @@ function _show({
title = null,
fullscreen = false,
customClassName = null,
showScrollbar = false,
noScroll = false,
}) {
return serviceImplementation._show({
content,
@@ -51,6 +55,8 @@ function _show({
title,
fullscreen,
customClassName,
showScrollbar,
noScroll,
});
}
@@ -8,6 +8,7 @@
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true]
* @property {object} [action=null]
*/
const name = 'UINotificationService';
@@ -34,7 +35,7 @@ const serviceImplementation = {
* Create and show a new UI notification; returns the
* ID of the created notification.
*
* @param {Notification} notification { title, message, duration, position, type, autoClose}
* @param {Notification} notification { title, message, duration, position, type, autoClose, action}
* @returns {number} id
*/
function _show({
@@ -44,6 +45,7 @@ function _show({
position = 'bottomRight',
type = 'info',
autoClose = true,
action = null,
}) {
return serviceImplementation._show({
title,
@@ -52,6 +54,7 @@ function _show({
position,
type,
autoClose,
action,
});
}
+12 -12
View File
@@ -21,12 +21,12 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
timer(120).then(now => {
timer(1200).then(now => {
const elapsed = now - start;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
});
const end = await timer(120);
expect(end - start > 240).toBe(true);
const end = await timer(1200);
expect(end - start > 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(2);
});
it('should prevent task execution when queue limit is reached', async () => {
@@ -34,15 +34,15 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bind(mockedTimeout);
const start = Date.now();
const promise = timer(120).then(time => time - start);
const promise = timer(1200).then(time => time - start);
try {
await timer(120);
await timer(1200);
} catch (e) {
expect(Date.now() - start < 120).toBe(true);
expect(Date.now() - start < 1200).toBe(true);
expect(e.message).toBe('Queue limit reached');
}
const elapsed = await promise;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
it('should safely bind tasks to the queue', async () => {
@@ -51,16 +51,16 @@ describe('Queue', () => {
const mockedTimeout = jest.fn(timeout);
const timer = queue.bindSafe(mockedTimeout, mockedErrorHandler);
const start = Date.now();
const promise = timer(120).then(time => time - start);
await timer(120);
expect(Date.now() - start < 120).toBe(true);
const promise = timer(1200).then(time => time - start);
await timer(1200);
expect(Date.now() - start < 1200).toBe(true);
expect(mockedErrorHandler).toBeCalledTimes(1);
expect(mockedErrorHandler).nthCalledWith(
1,
expect.objectContaining({ message: 'Queue limit reached' })
);
const elapsed = await promise;
expect(elapsed >= 120 && elapsed < 240).toBe(true);
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
expect(mockedTimeout).toBeCalledTimes(1);
});
});
+60
View File
@@ -3,6 +3,66 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.1...@ohif/ui@1.8.2) (2021-02-05)
**Note:** Version bump only for package @ohif/ui
## [1.8.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.0...@ohif/ui@1.8.1) (2021-01-21)
**Note:** Version bump only for package @ohif/ui
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.0...@ohif/ui@1.7.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03)
### Features
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.7...@ohif/ui@1.6.0) (2020-12-03)
### Features
* Add error boundary and retry logic for network failures during dynamic imports ([#2145](https://github.com/OHIF/Viewers/issues/2145)) ([4c07904](https://github.com/OHIF/Viewers/commit/4c079044f6ae2381c6054d8d77414100152d1d19))
## [1.5.7](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.6...@ohif/ui@1.5.7) (2020-11-02)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.5.7",
"version": "1.8.2",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -15,8 +15,8 @@ const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
const OHIFErrorBoundary = ({
context = 'OHIF',
onReset = () => {},
onError = () => {},
onReset = () => { },
onError = () => { },
fallbackComponent,
children,
}) => {
@@ -45,7 +45,7 @@ OHIFErrorBoundary.propTypes = {
onReset: PropTypes.func,
onError: PropTypes.func,
children: PropTypes.node.isRequired,
fallbackComponent: PropTypes.element,
fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.element]),
};
export default OHIFErrorBoundary;
@@ -0,0 +1,24 @@
.ErrorPage {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: var(--active-color);
}
.ErrorPage .error-container {
margin: 10px;
width: 50%;
height: 25%;
overflow: scroll;
border-radius: 15px;
border-color: var(--active-color);
border: 1px solid;
padding: 5px;
}
.ErrorPage .retry-icon {
cursor: pointer;
}
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon } from '@ohif/ui';
import './ErrorPage.css';
const ErrorPage = ({ error, title, description, onRetry }) => {
return (
<div className="ErrorPage">
{title && <h3>{title}</h3>}
<p>{description}</p>
<Icon
className="retry-icon"
name="rotate-right"
width="25px"
height="25px"
onClick={onRetry}
/>
{error && (
<div className="error-container">
<pre>{error.message}</pre>
<pre>{error.stack}</pre>
</div>
)}
</div>
);
};
ErrorPage.propTypes = {
error: PropTypes.object,
title: PropTypes.string,
description: PropTypes.string,
onRetry: PropTypes.func
};
ErrorPage.defaultProps = {
description: 'Oh snap, something went wrong, please try reloading',
onRetry: () => window.location.reload()
};
export default ErrorPage;
@@ -0,0 +1,2 @@
import ErrorPage from './ErrorPage';
export default ErrorPage;
+2
View File
@@ -16,6 +16,7 @@ import { SelectTree } from './selectTree';
import { SimpleDialog } from './simpleDialog';
import { OHIFModal } from './ohifModal';
import { ContextMenu } from './contextMenu';
import ErrorPage from './errorPage';
import {
PageToolbar,
StudyList,
@@ -58,4 +59,5 @@ export {
Tooltip,
AboutContent,
OHIFModal,
ErrorPage
};
@@ -27,13 +27,17 @@
&.OHIFModal-fullscreen
width: 95%
&__content
padding: 20px
max-height: 90vh;
overflow-x: hidden;
overflow-y: auto;
&.noScroll &__content {
overflow: hidden;
}
&:not(.visibleScrollbar) &__content
overflow-x: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar
@@ -156,19 +156,50 @@
transition: all 300ms ease;
}
.sb-success {
.sb-item .sb-action,
.sb-error .sb-action,
.sb-warning .sb-action,
.sb-info .sb-action,
.sb-success .sb-action {
margin-top: 10px;
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: none;
color: white;
}
.sb-success,
.sb-success .sb-action {
background-color: var(--snackbar-success);
}
.sb-error {
.sb-error,
.sb-error .sb-action {
background-color: var(--snackbar-error);
}
.sb-warning {
.sb-warning,
.sb-warning .sb-action {
background-color: var(--snackbar-warning);
}
.sb-info {
.sb-info,
.sb-info .sb-action {
background-color: var(--snackbar-info);
}
@@ -1,10 +1,14 @@
import React, { useState, useEffect } from 'react';
import React, { useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => {
onClose(options.id);
};
const handleClick = () => {
options.action.onClick({ ...options, close: handleClose });
};
useEffect(() => {
if (options.autoClose) {
setTimeout(() => {
@@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => {
</span>
{options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>}
{options.action && (
<button className="sb-action" onClick={handleClick}>
{options.action.label}
</button>
)}
</div>
);
};
@@ -34,6 +34,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
isOpen: true,
onClose: null,
closeButton: true,
showScrollbar: false,
title: null,
customClassName: '',
fullscreen: false,
@@ -81,13 +82,20 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
shouldCloseOnEsc,
fullscreen,
closeButton,
showScrollbar,
noScroll,
} = options;
return (
<Provider value={{ show, hide }}>
{ModalContent && (
<Modal
className={classNames(customClassName, ModalContent.className)}
className={classNames(
customClassName,
ModalContent.className,
{ visibleScrollbar: showScrollbar },
{ noScroll }
)}
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
title={title}
@@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => {
autoClose: true,
position: 'bottomRight',
type: SnackbarTypes.INFO,
action: null,
};
const [count, setCount] = useState(1);
@@ -37,6 +38,10 @@ const SnackbarProvider = ({ children, service }) => {
return null;
}
if (options.type === 'error') {
console.error(options.error);
}
const newItem = {
...DEFAULT_OPTIONS,
...options,
+8 -1
View File
@@ -28,7 +28,13 @@ class Range extends Component {
className="range"
/>
{this.props.showPercentage && <span>{`${this.state.value}%`}</span>}
{this.props.showValue && <span>{this.state.value}</span>}
{this.props.showValue && (
<span>
{this.props.valueRenderer
? this.props.valueRenderer(this.state.value)
: this.state.value}
</span>
)}
</>
);
}
@@ -40,6 +46,7 @@ Range.propTypes = {
max: PropTypes.number.isRequired,
step: PropTypes.number,
id: PropTypes.string,
valueRenderer: PropTypes.func,
onChange: PropTypes.func,
showPercentage: PropTypes.bool,
showValue: PropTypes.bool,
+8 -2
View File
@@ -29,7 +29,8 @@ import {
Tooltip,
AboutContent,
OHIFModal,
ErrorBoundary
ErrorBoundary,
ErrorPage
} from './components';
import { useDebounce, useMedia } from './hooks';
@@ -53,6 +54,7 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
import Toolbar from './viewer/Toolbar.js';
import ToolbarButton from './viewer/ToolbarButton.js';
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
import { asyncComponent, retryImport } from './utils/asyncComponent';
import {
SnackbarProvider,
useSnackbarContext,
@@ -112,7 +114,6 @@ export {
ToolbarSection,
Tooltip,
AboutContent,
ViewerbaseDragDropContext,
SnackbarProvider,
useSnackbarContext,
withSnackbar,
@@ -125,7 +126,12 @@ export {
withDialog,
useDialog,
ErrorBoundary,
ErrorPage,
// Hooks
useDebounce,
useMedia,
// Utils
ViewerbaseDragDropContext,
asyncComponent,
retryImport
};
@@ -0,0 +1,62 @@
import React, { useState, useEffect } from 'react';
import { ErrorPage } from '@ohif/ui';
export const retryImport = (fn, retriesLeft = 5, interval = 1000) =>
new Promise((resolve, reject) => {
fn().then(resolve).catch((error) => {
setTimeout(() => {
if (retriesLeft === 1) {
/* reject('maximum retries exceeded'); */
reject(error);
return;
}
/* Passing on "reject" is the important part */
retry(fn, retriesLeft - 1, interval).then(resolve, reject);
}, interval);
});
});
const onError = (error, setState) => setState({ component: ErrorPage });
/**
* We use this function to lazy load the import of a component to leverage 'Code Splitting'
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
const asyncComponent = (importComponent, options = { onError }) => props => {
const [state, setState] = useState({ component: null });
const isFunction = item => typeof item === 'function';
const isChunkError = error => error.toString().indexOf('ChunkLoadError') > -1;
useEffect(() => {
const addDynamicallyLoadedComponentToState = async () => {
try {
const { default: component } = await importComponent();
setState({ component });
if (options.onLoaded && isFunction(options.onLoaded)) {
options.onLoaded(component);
}
} catch (error) {
console.error('[AsyncComponent] Failed to import chunk:', error);
if (options.onError && isFunction(options.onError)) {
options.onError(error, setState);
return;
}
if (isChunkError(error)) {
console.error('[AsyncComponent] Reloading due to chunk error');
window.location.reload();
}
}
};
addDynamicallyLoadedComponentToState();
}, []);
const Component = state.component;
return Component ? <Component {...props} /> : null;
};
export default asyncComponent;
@@ -0,0 +1,2 @@
export { default as asyncComponent } from './asyncComponent';
export { retryImport } from './asyncComponent';
+180
View File
@@ -3,6 +3,186 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.8.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.11...@ohif/viewer@4.8.12) (2021-03-03)
**Note:** Version bump only for package @ohif/viewer
## [4.8.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.10...@ohif/viewer@4.8.11) (2021-02-25)
**Note:** Version bump only for package @ohif/viewer
## [4.8.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.9...@ohif/viewer@4.8.10) (2021-02-08)
**Note:** Version bump only for package @ohif/viewer
## [4.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.8...@ohif/viewer@4.8.9) (2021-02-05)
**Note:** Version bump only for package @ohif/viewer
## [4.8.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.7...@ohif/viewer@4.8.8) (2021-02-05)
**Note:** Version bump only for package @ohif/viewer
## [4.8.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.6...@ohif/viewer@4.8.7) (2021-01-28)
### Bug Fixes
* **config:** Update Google Healthcare API endpoint from v1beta1 to v1 ([#2256](https://github.com/OHIF/Viewers/issues/2256)) ([83b7d9e](https://github.com/OHIF/Viewers/commit/83b7d9e8b4240a928eb43735280000e97f186d90))
## [4.8.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.5...@ohif/viewer@4.8.6) (2021-01-26)
**Note:** Version bump only for package @ohif/viewer
## [4.8.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.4...@ohif/viewer@4.8.5) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.3...@ohif/viewer@4.8.4) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.2...@ohif/viewer@4.8.3) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.1...@ohif/viewer@4.8.2) (2021-01-21)
**Note:** Version bump only for package @ohif/viewer
## [4.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.0...@ohif/viewer@4.8.1) (2020-12-10)
**Note:** Version bump only for package @ohif/viewer
# [4.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.2...@ohif/viewer@4.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [4.7.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.1...@ohif/viewer@4.7.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.0...@ohif/viewer@4.7.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
# [4.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.3...@ohif/viewer@4.7.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [4.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.2...@ohif/viewer@4.6.3) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.1...@ohif/viewer@4.6.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
# [4.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.29...@ohif/viewer@4.6.0) (2020-12-03)
### Features
* Add error boundary and retry logic for network failures during dynamic imports ([#2145](https://github.com/OHIF/Viewers/issues/2145)) ([4c07904](https://github.com/OHIF/Viewers/commit/4c079044f6ae2381c6054d8d77414100152d1d19))
## [4.5.29](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.28...@ohif/viewer@4.5.29) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.5.28](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.27...@ohif/viewer@4.5.28) (2020-11-30)
**Note:** Version bump only for package @ohif/viewer
+6 -1
View File
@@ -6,5 +6,10 @@
"requestTimeout": 10000,
"responseTimeout": 10000,
"projectId": "4oe38f",
"video": false
"video": false,
"reporter": "junit",
"reporterOptions": {
"mochaFile": "cypress/results/test-output.xml",
"toConsole": true
}
}
@@ -3,7 +3,7 @@ describe('OHIF Cornerstone Hotkeys', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -3,12 +3,18 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
cy.initCornerstoneToolsAliases();
cy.initCommonElementsAliases();
cy.get('[data-cy="thumbnail-list"]:nth-child(1)').click();
const expectedText = 'Ser: 1';
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
cy.resetViewport();
});
@@ -49,7 +55,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
it('checks if Stack Scroll tool will navigate across all series in the viewport', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@stackScrollBtn')
.click()
.then($stackScrollBtn => {
@@ -85,7 +91,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@levelsBtn')
.click()
.then($levelsBtn => {
@@ -106,7 +112,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
it('checks if Pan tool will move the image inside the viewport', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@panBtn')
.click()
.then($panBtn => {
@@ -120,7 +126,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@lengthBtn')
.click()
.then($lengthbtn => {
@@ -147,7 +153,7 @@ describe('OHIF Cornerstone Toolbar', () => {
});
it('checks if Angle annotation can be added on viewport and on measurements panel', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@angleBtn')
.click()
.then($angleBtn => {
@@ -192,7 +198,8 @@ describe('OHIF Cornerstone Toolbar', () => {
//Click on button
cy.get('@cineBtn').click();
//Vefiry if cine control overlay is being displayed
// Verify if cine control overlay is being displayed
cy.get('.cine-controls')
.as('cineControls')
.should('be.visible');
@@ -253,13 +260,15 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('@cineBtn')
.click()
.then(() => {
//Vefiry if cine control overlay is hidden
cy.get('@cineControls').should('not.be.visible');
// Verify that cine control overlay is hidden
cy.get('@cineControls').should('not.exist');
});
});
it('checks if More button will prompt a modal with secondary tools', () => {
//Click on More button
cy.get('@moreBtn').click();
//Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay')
.as('toolbarOverlay')
@@ -288,9 +297,10 @@ describe('OHIF Cornerstone Toolbar', () => {
});
//Verify if overlay is hidden
cy.get('@toolbarOverlay').should('not.be.visible');
cy.get('@toolbarOverlay').should('not.exist');
});
it('checks if Layout tool will multiply the number of viewports displayed', () => {
//Click on Layout button and verify if overlay is displayed
cy.get('@layoutBtn')
@@ -415,7 +425,7 @@ describe('OHIF Cornerstone Toolbar', () => {
// TODO: We need a seperate test server for this to work.
// As anyone can save measurements on a different slice.
cy.get('.measurementItem'); //.should('not.exist');
//cy.get('.measurementItem'); //.should('not.exist');
//Close More button overlay
cy.get('@moreBtn').click();
@@ -439,6 +449,9 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('@viewportInfoMidLeft').should('contains.text', 'F');
cy.get('@viewportInfoMidTop').should('contains.text', 'R');
});
//Click on More button to close it
cy.get('@moreBtn').click();
});
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
@@ -451,6 +464,10 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('[data-cy="flip h"]').click();
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
cy.get('@viewportInfoMidTop').should('contains.text', 'H');
//Click on More button to close it
cy.get('@moreBtn').click();
cy.get('.tooltip-toolbar-overlay').should('not.exist');
});
it('check if Flip V tool will flip the image vertically in the viewport', () => {
@@ -463,5 +480,9 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('[data-cy="flip v"]').click();
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
cy.get('@viewportInfoMidTop').should('contains.text', 'F');
//Click on More button to close it
cy.get('@moreBtn').click();
cy.get('.tooltip-toolbar-overlay').should('not.exist');
});
});
@@ -3,7 +3,7 @@ describe('OHIF Download Snapshot File', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -22,51 +22,54 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(3);
expect($list.length).to.be.eq(1);
expect($list).to.contain('Juno');
});
});
it('searches MRN with exact string', function() {
cy.get('@MRN').type('ProstateX-0000');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('ProstateX-0000');
});
});
it('searches Accession with exact string', function() {
cy.get('@AccessionNumber').type('0000481914');
cy.get('@MRN').type('0000003');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000481914');
expect($list).to.contain('0000003');
});
});
it('searches Accession with exact string', function() {
cy.get('@AccessionNumber').type('0000155811');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000155811');
});
});
it('searches Modality with camel case', function() {
cy.get('@modalities').type('Mr');
cy.get('@modalities').type('Ct');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
expect($list.length).to.be.eq(9);
expect($list).to.contain('MR');
expect($list.length).to.be.greaterThan(1);
expect($list).to.contain('CT');
});
});
/*
TODO: Currently broken in dicomweb-server
it('searches Description with exact string', function() {
cy.get('@StudyDescription').type('CHEST');
cy.get('@StudyDescription').type('PETCT');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('CHEST');
expect($list.length).to.be.eq(1);
expect($list).to.contain('PETCT');
});
});
*/
it('changes Rows per page and checks the study count', function() {
//Show Rows per page options
@@ -128,52 +131,60 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(3);
expect($list.length).to.be.eq(1);
expect($list).to.contain('Juno');
});
});
it('searches MRN with with exact string', function() {
cy.get('@patientNameOrMRN').type('ProstateX-0000');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(6);
expect($list).to.contain('ProstateX-0000');
});
});
it('searches Modality with exact string', function() {
cy.get('@accessionModalityDescription').type('MR');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
expect($list.length).to.be.eq(9);
expect($list).to.contain('MR');
});
});
it('searches Accession with exact string', function() {
cy.get('@accessionModalityDescription').type('0000481914');
cy.get('@patientNameOrMRN').type('Juno');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000481914');
expect($list).to.contain('Juno');
});
});
it('searches Description with exact string', function() {
cy.get('@accessionModalityDescription').type('CHEST');
it('searches Modality with exact string', function() {
cy.get('@accessionModalityDescription').type('CT');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(2);
expect($list).to.contain('CHEST');
expect($list.length).to.be.eq(1);
expect($list).to.contain('CT');
});
});
/*
/*
TODO: Currently broken in dicomweb-server
it('searches Accession with exact string', function() {
cy.get('@accessionModalityDescription').type('0000155811');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000155811');
});
});
*/
/*
TODO: Currently broken in dicomweb-server
it('searches Description with exact string', function() {
cy.get('@accessionModalityDescription').type('PETCT');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('PETCT');
});
});
*/
it('changes Rows per page and checks the study count', function() {
//Show Rows per page options
const pageRows = [25, 50, 100];
@@ -3,12 +3,12 @@ describe('OHIF Study Viewer Page', function() {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(function() {
cy.initCommonElementsAliases();
cy.resetViewport();
cy.resetViewport().wait(50);
});
it('checks if series thumbnails are being displayed', function() {
@@ -50,10 +50,10 @@ describe('OHIF Study Viewer Page', function() {
.click();
// Click "Relabel"
cy.get('.btnAction')
cy.get('.btnAction', { timeout: 10000 })
.first()
.contains('Relabel')
.click();
.click().should('be.visible');
// Search for "Bone"
cy.get('.searchInput').type('Bone');
@@ -68,11 +68,20 @@ describe('OHIF Study Viewer Page', function() {
// Verify if 'Bone' label was added
cy.get('.measurementLocation').should('contain.text', 'Bone');
// Remove the measurement we just added
cy.get('.btnAction')
.last()
.contains('Delete')
.click()
// Close panel
cy.get('@measurementsBtn').click();
cy.get('@measurementsPanel').should('not.be.enabled');
});
/*
TODO: Not sure why this is failing
it('checks if Description can be added to measurement item under Measurements panel', () => {
cy.addLengthMeasurement(); //Adding measurement in the viewport
cy.get('@measurementsBtn').click();
@@ -94,7 +103,19 @@ describe('OHIF Study Viewer Page', function() {
//Verify if descriptionText was added
cy.get('.measurementLocation').should('contain.text', descriptionText);
// Remove the measurement we just added
cy.get('.btnAction')
.last()
.contains('Delete')
.click()
// Close panel
cy.get('@measurementsBtn').click();
cy.get('@measurementsPanel').should('not.be.enabled');
});
*/
it('checks if measurement item can be deleted through the context menu on the viewport', function() {
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
@@ -108,6 +129,7 @@ describe('OHIF Study Viewer Page', function() {
.trigger('mouseup', x1, y1, {
which: 3,
})
.wait(300)
.then(() => {
//Contextmenu is visible
cy.get('.ToolContextMenu').should('be.visible');
@@ -122,12 +144,9 @@ describe('OHIF Study Viewer Page', function() {
cy.get('@measurementsBtn').click();
//Verify measurements was removed from panel
cy.get('.measurementItem');
// TODO: We need a seperate test server for this to work.
// As anyone can save measurements on a different slice.
// .should('not.exist')
// .log('Annotation removed with success');
cy.get('.measurementItem')
.should('not.exist')
.log('Annotation successfully removed');
//Close panel
cy.get('@measurementsBtn').click();
@@ -228,7 +247,7 @@ describe('OHIF Study Viewer Page', function() {
});
const expectedText =
'Ser: 5Img: 1 12/12512 x 512Loc: -15.40 mm Thick: 4.00 mm'; //'Img: 13 13/13';
'Ser: 2Img: 13 13/13512 x 512Loc: 18.40 mm Thick: 3.00 mm'; //'Img: 13 13/13';
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
});
@@ -246,7 +265,8 @@ describe('OHIF Study Viewer Page', function() {
.trigger('mousemove', 'center', { which: 3 })
.trigger('mouseup');
const expectedText = 'Zoom: 301%';
const expectedText = 'Zoom: 442%';
cy.get('@viewportInfoBottomRight').should('contains.text', expectedText);
});
@@ -301,6 +321,6 @@ describe('OHIF Study Viewer Page', function() {
//close modal
cy.get('[data-cy="close-button"]').click();
cy.get('@aboutOverlay').should('not.be.enabled');
cy.get('@aboutOverlay').should('not.exist');
});
});
@@ -184,7 +184,7 @@ describe('OHIF User Preferences', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
cy.initCommonElementsAliases();
// Check if application is in Spanish
@@ -217,7 +217,7 @@ describe('OHIF User Preferences', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -583,7 +583,7 @@ describe('OHIF User Preferences', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -1,9 +1,12 @@
/*
Temporarily disabling as we transition to containerized PACS for E2E tests
describe('OHIF HTML Extension', () => {
before(() => {
cy.openStudyInViewer(
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
// TODO -> Commented these out until we get a seperate temporary PACS running on the CI.
@@ -79,3 +82,4 @@ describe('OHIF HTML Extension', () => {
// cy.screenshot('PDF Extension - Should load PDF file');
// });
// });
*/
@@ -1,3 +1,6 @@
/*
Temporarily disabling as we transition to containerized PACS for E2E tests
describe('OHIF Microscopy Extension', () => {
before(() => {
cy.openStudyModality('SM');
@@ -27,3 +30,4 @@ describe('OHIF Microscopy Extension', () => {
cy.screenshot('Microscopy Extension - Should display loaded canvas');
});
});
*/
@@ -3,10 +3,14 @@ describe('OHIF VTK Extension', () => {
cy.checkStudyRouteInViewer(
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'
);
cy.expectMinimumThumbnails(7);
cy.expectMinimumThumbnails(3);
// TODO: Added 1s wait because we are loading initial series list
// from QIDO-RS, which is breaking some cypress checks
//Waiting for the desired thumbnail content to be displayed
cy.get('[data-cy="thumbnail-list"]').should($list => {
cy.get('[data-cy="thumbnail-list"]').wait(1000).should($list => {
expect($list).to.contain('CT WB 5.0 B35f');
});
@@ -15,7 +19,7 @@ describe('OHIF VTK Extension', () => {
// has data from a drag-n-drop
// Drag and drop third thumbnail into first viewport
cy.get('[data-cy="thumbnail-list"]')
.contains('CT WB 5.0 B35f')
.eq(2)
.drag('.viewport-drop-target');
//Select 2D MPR button
@@ -3,7 +3,7 @@ describe('OHIF Save Measurements', function() {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Cornerstone Hotkeys', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -13,7 +13,7 @@ describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
});
it('checks if Pan tool will move the image inside the viewport', () => {
//Click on button and vefiry if icon is active on toolbar
//Click on button and verify if icon is active on toolbar
cy.get('@panBtn')
.click()
.then($panBtn => {
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Download Snapshot File', () => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -1,3 +1,6 @@
/*
Temporarily disabling as we transition to containerized PACS for E2E tests
describe('Visual Regression - OHIF Microscopy Extension', () => {
before(() => {
cy.openStudyModality('SM');
@@ -20,3 +23,4 @@ describe('Visual Regression - OHIF Microscopy Extension', () => {
);
});
});
*/
@@ -1,3 +1,6 @@
/*
Temporarily disabling as we transition to containerized PACS for E2E tests
describe('Visual Regression - OHIF PDF Extension', () => {
before(() => {
cy.checkStudyRouteInViewer(
@@ -20,3 +23,4 @@ describe('Visual Regression - OHIF PDF Extension', () => {
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
});
});
*/
@@ -1,3 +1,6 @@
/*
Temporarily disabling as we transition to containerized PACS for E2E tests
describe('Visual Regression - OHIF VTK Extension', () => {
before(() => {
cy.checkStudyRouteInViewer(
@@ -91,3 +94,4 @@ describe('Visual Regression - OHIF VTK Extension', () => {
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
});
});
*/
@@ -13,7 +13,7 @@ describe('Visual Regression - OHIF Routes', function() {
);
cy.server();
cy.route('GET', '**/ TESTStudy; /**').as('getTESTStudy');
cy.route('GET', '*TESTStudy; /**').as('getTESTStudy');
cy.wait('@getTESTStudy.all');
cy.get('@getTESTStudy').should($route => {
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Study Viewer Page', function() {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(function() {
@@ -31,6 +31,6 @@ describe('Visual Regression - OHIF Study Viewer Page', function() {
//close modal
cy.get('[data-cy="close-button"]').click();
cy.get('@aboutOverlay').should('not.be.enabled');
cy.get('@aboutOverlay').should('not.exist');
});
});
@@ -9,6 +9,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.openPreferences();
});
afterEach(() => {
// Close User Preferences modal
cy.closePreferences();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
@@ -52,7 +57,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
context('Study Viewer Page', function() {
before(() => {
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.expectMinimumThumbnails(5);
cy.expectMinimumThumbnails(3);
});
beforeEach(() => {
@@ -64,6 +69,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.openPreferences();
});
afterEach(() => {
// Close User Preferences modal
cy.closePreferences();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
@@ -150,13 +160,13 @@ describe('Visual Regression - OHIF User Preferences', () => {
// Set new hotkey for 'Next Image Viewport' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
'Next Image Viewport',
'Next Viewport',
'{shift}{rightarrow}'
);
// Set new hotkey for 'Previous Image Viewport' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
'Previous Image Viewport',
'Previous Viewport',
'{shift}{leftarrow}'
);
+6 -4
View File
@@ -14,20 +14,22 @@ let percyHealthCheck = require('@percy/cypress/task');
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('before:browser:launch', (browser = {}, args) => {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'chrome') {
// `args` is an araay of all the arguments
// that will be passed to Chrome when it launchers
args.push('--start-fullscreen');
launchOptions.args.push('--start-fullscreen');
// whatever you return here becomes the new args
return args;
return launchOptions;
}
if (browser.name === 'chromium') {
const newArgs = args.filter(arg => arg !== '--disable-gpu');
newArgs.push('--ignore-gpu-blacklist');
return newArgs;
launchOptions.args = newArgs;
return launchOptions;
}
});
Whitespace-only changes.
+24 -4
View File
@@ -149,10 +149,11 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
const [x1, y1] = firstClick;
const [x2, y2] = secondClick;
// TODO: Added a wait which appears necessary in Cornerstone Tools >4?
cy.wrap($viewport)
.click(x1, y1, { force: true })
.click(x1, y1).wait(100)
.trigger('mousemove', { clientX: x2, clientY: y2 })
.click(x2, y2, { force: true });
.click(x2, y2).wait(100);
});
});
@@ -242,6 +243,8 @@ Cypress.Commands.add('resetViewport', () => {
cy.get('[data-cy="reset"]')
.as('resetBtn')
.click();
cy.get('.tooltip-toolbar-overlay').should('not.exist');
});
Cypress.Commands.add('imageZoomIn', () => {
@@ -466,6 +469,24 @@ Cypress.Commands.add('openPreferences', () => {
});
});
Cypress.Commands.add('closePreferences', () => {
cy.log('Close User Preferences Modal');
cy.get('body').then(body => {
// Close notification if displayed
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
// Close User Preferences Modal (if displayed)
if (body.find('.OHIFModal__header').length > 0) {
cy.get('[data-cy="close-button"]').click({ force: true });
}
});
});
Cypress.Commands.add('selectPreferencesTab', tabAlias => {
cy.initPreferencesModalAliases();
cy.get(tabAlias)
@@ -527,8 +548,7 @@ Cypress.Commands.add(
.parent()
.find('input') // closest input to that label
.type(shortcut, { force: true }); // Set new shortcut for that function
})
.blur();
});
}
);
+12 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "4.5.28",
"version": "4.8.12",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -30,6 +30,7 @@
"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/**/*'",
"test:e2e:script-tag": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'",
"test:e2e:local": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'",
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
"test:e2e:serve": "serve -n -l 3000 -s dist",
@@ -48,18 +49,18 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.10.7",
"@ohif/extension-cornerstone": "^2.9.6",
"@ohif/core": "^2.12.5",
"@ohif/extension-cornerstone": "^2.10.3",
"@ohif/extension-debugging": "^0.1.6",
"@ohif/extension-dicom-html": "^1.2.9",
"@ohif/extension-dicom-microscopy": "^0.51.3",
"@ohif/extension-dicom-html": "^1.3.3",
"@ohif/extension-dicom-microscopy": "^0.51.4",
"@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.4.9",
"@ohif/extension-dicom-segmentation": "^0.4.7",
"@ohif/extension-dicom-rt": "^0.6.6",
"@ohif/extension-dicom-segmentation": "^0.5.4",
"@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.8.0",
"@ohif/extension-vtk": "^1.10.6",
"@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.5.7",
"@ohif/ui": "^1.8.2",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
@@ -67,7 +68,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5",
"dcmjs": "0.18.2",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8",
@@ -92,7 +93,7 @@
"vtk.js": "^11.14.0"
},
"devDependencies": {
"cypress": "^3.8.0",
"cypress": "^6.0.0",
"gh-pages": "2.0.1",
"identity-obj-proxy": "3.0.x",
"lodash": "4.17.15",
@@ -0,0 +1,125 @@
window.config = {
routerBasename: '/',
extensions: [],
showStudyList: true,
servers: {
dicomWeb: [
{
name: 'dicomweb_server',
wadoUriRoot: 'http://localhost:5985',
qidoRoot: 'http://localhost:5985',
wadoRoot: 'http://localhost:5985',
qidoSupportsIncludeField: false,
imageRendering: 'wadouri',
thumbnailRendering: 'wadouri',
enableStudyLazyLoad: true,
},
],
},
// Extensions should be able to suggest default values for these?
// Or we can require that these be explicitly set
hotkeys: [
// ~ Global
{
commandName: 'incrementActiveViewport',
label: 'Next Viewport',
keys: ['right'],
},
{
commandName: 'decrementActiveViewport',
label: 'Previous Viewport',
keys: ['left'],
},
// Supported Keys: https://craig.is/killing/mice
// ~ Cornerstone Extension
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
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'] },
// clearAnnotations
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
// firstImage
// lastImage
{
commandName: 'previousViewportDisplaySet',
label: 'Previous Series',
keys: ['pagedown'],
},
{
commandName: 'nextViewportDisplaySet',
label: 'Next Series',
keys: ['pageup'],
},
// ~ Cornerstone Tools
{ commandName: 'setZoomTool', 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'],
},
],
cornerstoneExtensionConfig: {},
// Following property limits number of simultaneous series metadata requests.
// For http/1.x-only servers, set this to 5 or less to improve
// on first meaningful display in viewer
// If the server is particularly slow to respond to series metadata
// requests as it extracts the metadata from raw files everytime,
// try setting this to even lower value
// Leave it undefined for no limit, sutiable for HTTP/2 enabled servers
// maxConcurrentMetadataRequests: 5,
};
+1 -1
View File
@@ -1,7 +1,7 @@
window.config = {
routerBasename: '/',
enableGoogleCloudAdapter: true,
healthcareApiEndpoint: 'https://healthcare.googleapis.com/v1beta1',
healthcareApiEndpoint: 'https://healthcare.googleapis.com/v1',
servers: {
// This is an array, but we'll only use the first entry for now
dicomWeb: [],
+6 -7
View File
@@ -5,9 +5,8 @@ import { Route, Switch } from 'react-router-dom';
import { NProgress } from '@tanem/react-nprogress';
import { CSSTransition } from 'react-transition-group';
import { connect } from 'react-redux';
import { ViewerbaseDragDropContext, ErrorBoundary } from '@ohif/ui';
import { ViewerbaseDragDropContext, ErrorBoundary, asyncComponent, retryImport } from '@ohif/ui';
import { SignoutCallbackComponent } from 'redux-oidc';
import asyncComponent from './components/AsyncComponent.js';
import * as RoutesUtil from './routes/routesUtil';
import NotFound from './routes/NotFound.js';
@@ -18,7 +17,7 @@ import './theme-tide.css';
// Contexts
import AppContext from './context/AppContext';
const CallbackPage = asyncComponent(() =>
import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js')
retryImport(() => import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js'))
);
class OHIFStandaloneViewer extends Component {
@@ -203,10 +202,10 @@ class OHIFStandaloneViewer extends Component {
{match === null ? (
<></>
) : (
<ErrorBoundary context={match.url}>
<Component match={match} location={this.props.location} />
</ErrorBoundary>
)}
<ErrorBoundary context={match.url}>
<Component match={match} location={this.props.location} />
</ErrorBoundary>
)}
</CSSTransition>
)}
</Route>
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+26 -31
View File
@@ -1,41 +1,36 @@
import './SidePanel.css';
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class SidePanel extends Component {
static propTypes = {
from: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
children: PropTypes.node,
width: PropTypes.string,
};
const SidePanel = ({ from, isOpen, children, width }) => {
const fromSideClass = from === 'right' ? 'from-right' : 'from-left';
render() {
const fromSideClass =
this.props.from === 'right' ? 'from-right' : 'from-left';
const styles = width
? {
maxWidth: width,
marginRight: isOpen ? '0' : Number.parseInt(width) * -1,
}
: {};
const styles = this.props.width
? {
maxWidth: this.props.width,
marginRight: this.props.isOpen
? '0'
: Number.parseInt(this.props.width) * -1,
}
: {};
return (
<section
style={styles}
className={classNames('sidepanel', fromSideClass, {
'is-open': isOpen,
})}
>
{children}
</section>
);
};
return (
<section
style={styles}
className={classNames('sidepanel', fromSideClass, {
'is-open': this.props.isOpen,
})}
>
{this.props.children}
</section>
);
}
}
SidePanel.propTypes = {
from: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
children: PropTypes.node,
width: PropTypes.string,
};
export default SidePanel;
@@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) {
const ViewportGrid = function (props) {
const {
activeViewportIndex,
availablePlugins,
@@ -48,6 +48,7 @@ const ViewportGrid = function(props) {
title: 'Error loading derived display set:',
message: error.message,
type: 'error',
error,
autoClose: false,
});
});
@@ -86,7 +86,7 @@ class ToolbarRow extends Component {
// Note: This does not cleanly handle `studies` prop updating with panel open
const isDisabled =
typeof menuOption.isDisabled === 'function' &&
menuOption.isDisabled(this.props.studies);
menuOption.isDisabled(this.props.studies, this.props.activeViewport);
if (hasActiveContext && !isDisabled) {
const menuOptionEntry = {
@@ -114,25 +114,30 @@ class ToolbarRow extends Component {
prevProps.activeContexts !== this.props.activeContexts;
const prevStudies = prevProps.studies;
const prevActiveViewport = prevProps.activeViewport;
const activeViewport = this.props.activeViewport;
const studies = this.props.studies;
const seriesPerStudyCount = this.seriesPerStudyCount;
let studiesUpdated = false;
let shouldUpdate = false;
if (prevStudies.length !== studies.length) {
studiesUpdated = true;
if (
prevStudies.length !== studies.length ||
prevActiveViewport !== activeViewport
) {
shouldUpdate = true;
} else {
for (let i = 0; i < studies.length; i++) {
if (studies[i].series.length !== seriesPerStudyCount[i]) {
seriesPerStudyCount[i] = studies[i].series.length;
studiesUpdated = true;
shouldUpdate = true;
break;
}
}
}
if (studiesUpdated) {
if (shouldUpdate) {
this.updateButtonGroups();
}
@@ -83,6 +83,8 @@ class Viewer extends Component {
disassociate: this.disassociateStudy,
},
});
this._getActiveViewport = this._getActiveViewport.bind(this);
}
state = {
@@ -214,6 +216,10 @@ class Viewer extends Component {
}
}
_getActiveViewport() {
return this.props.viewports[this.props.activeViewportIndex];
}
render() {
let VisiblePanelLeft, VisiblePanelRight;
const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL];
@@ -262,6 +268,9 @@ class Viewer extends Component {
{/* TOOLBAR */}
<ErrorBoundaryDialog context="ToolbarRow">
<ToolbarRow
activeViewport={
this.props.viewports[this.props.activeViewportIndex]
}
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
isRightSidePanelOpen={this.state.isRightSidePanelOpen}
selectedLeftSidePanel={
@@ -341,6 +350,10 @@ class Viewer extends Component {
viewports={this.props.viewports}
studies={this.props.studies}
activeIndex={this.props.activeViewportIndex}
activeViewport={
this.props.viewports[this.props.activeViewportIndex]
}
getActiveViewport={this._getActiveViewport}
/>
)}
</SidePanel>
@@ -5,10 +5,11 @@ import usePrevious from '../customHooks/usePrevious';
import ConnectedViewer from './ConnectedViewer.js';
import PropTypes from 'prop-types';
import { extensionManager } from './../App.js';
import { useSnackbarContext } from '@ohif/ui';
import { useSnackbarContext, ErrorPage } from '@ohif/ui';
// Contexts
import AppContext from '../context/AppContext';
import NotFound from '../routes/NotFound';
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
@@ -291,7 +292,7 @@ function ViewerRetrieveStudyData({
})
.catch(error => {
if (error && !error.isCanceled) {
setError(true);
setError(error);
log.error(error);
}
});
@@ -358,13 +359,13 @@ function ViewerRetrieveStudyData({
})
.catch(error => {
if (error && !error.isCanceled) {
setError(true);
setError(error);
log.error(error);
}
});
} catch (error) {
if (error) {
setError(true);
setError(error);
log.error(error);
}
}
@@ -411,7 +412,12 @@ function ViewerRetrieveStudyData({
}, []);
if (error) {
return <div>Error: {JSON.stringify(error)}</div>;
const content = JSON.stringify(error);
if (content.includes('404') || content.includes('NOT_FOUND')) {
return <NotFound />;
}
return <NotFound message="Failed to retrieve study data" />;
}
return (
+11 -11
View File
@@ -1,32 +1,32 @@
import asyncComponent from '../components/AsyncComponent.js';
import { asyncComponent, retryImport } from '@ohif/ui';
import OHIF from '@ohif/core';
const { urlUtil: UrlUtil } = OHIF.utils;
// Dynamic Import Routes (CodeSplitting)
const IHEInvokeImageDisplay = asyncComponent(() =>
import(
/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js'
retryImport(() =>
import(/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js')
)
);
const ViewerRouting = asyncComponent(() =>
import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js')
retryImport(() => import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js'))
);
const StudyListRouting = asyncComponent(() =>
import(
retryImport(() => import(
/* webpackChunkName: "StudyListRouting" */ '../studylist/StudyListRouting.js'
)
))
);
const StandaloneRouting = asyncComponent(() =>
import(
retryImport(() => import(
/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js'
)
))
);
const ViewerLocalFileData = asyncComponent(() =>
import(
retryImport(() => import(
/* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js'
)
))
);
const reload = () => window.location.reload();
@@ -336,12 +336,6 @@ async function getStudyList(
} = filters;
const sortFieldName = sort.fieldName || 'PatientName';
const sortDirection = sort.direction || 'desc';
const studyDateFrom =
filters.studyDateFrom ||
moment()
.subtract(25000, 'days')
.toDate();
const studyDateTo = filters.studyDateTo || new Date();
const mappedFilters = {
PatientID: filters.PatientID,
@@ -350,8 +344,8 @@ async function getStudyList(
StudyDescription: filters.StudyDescription,
ModalitiesInStudy: filters.modalities,
// NEVER CHANGE
studyDateFrom,
studyDateTo,
studyDateFrom: filters.studyDateFrom,
studyDateTo: filters.studyDateTo,
limit: rowsPerPage,
offset: pageNumber * rowsPerPage,
fuzzymatching: server.supportsFuzzyMatching === true,
+362 -250
View File
File diff suppressed because it is too large. Load diff