Compare commits

..
Author SHA1 Message Date
ohif-bot 0fc7d93d73 chore(release): publish [skip ci]
- @ohif/extension-dicom-pdf@1.1.2
 - @ohif/extension-vtk@1.12.12
 - @ohif/core@2.16.8
 - @ohif/viewer@4.12.13
2022-01-29 01:06:23 +00:00
Bill Wallace 33307d3cd2 Feat/v2 e2e static tests (#2684)
Use the static data for hte e2e tests
2022-01-28 20:02:30 -05:00
ohif-bot c3ce909434 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.12.4
 - @ohif/extension-dicom-html@1.3.15
 - @ohif/extension-dicom-rt@0.7.6
 - @ohif/extension-dicom-segmentation@0.7.6
 - @ohif/extension-dicom-tag-browser@0.2.17
 - @ohif/extension-vtk@1.12.11
 - @ohif/core@2.16.7
 - @ohif/viewer@4.12.12
2022-01-20 09:18:41 +00:00
Davide Punzo ad2c750503 IDC update dcmjs to v0.19.2 (#2672)
* IDC: update dcmjs with SEG loading regression fix
2022-01-20 10:15:19 +01:00
ohif-bot cceb61f123 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.7.5
 - @ohif/viewer@4.12.11
2022-01-19 09:29:16 +00:00
Davide Punzo 13bcba8c39 IDC #2657: fix rt struct panel update (#2666)
* Fix observation in RT panel
2022-01-19 10:25:42 +01:00
ohif-bot d2db026158 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.10
 - @ohif/core@2.16.6
 - @ohif/viewer@4.12.10
2022-01-13 13:21:38 +00:00
Ștefan Silviu-AlexandruandStefan Silviu e4b0552db4 feature: configurable max prefetch request count (#2632)
* feature: configurable max prefetch request count

* update name and default value for prefetch number

Co-authored-by: Stefan Silviu <silviu.stefan@atta.systems>
2022-01-13 08:18:29 -05:00
ohif-bot d7ba2fac69 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.9
 - @ohif/core@2.16.5
 - @ohif/viewer@4.12.9
2021-12-23 22:25:30 +00:00
Alireza 761d54fbbf fix: DICOMFileLoadingListener typo in method name (#2647) 2021-12-23 17:22:35 -05:00
ohif-bot 79548d2380 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.8
 - @ohif/core@2.16.4
 - @ohif/viewer@4.12.8
2021-12-23 20:38:37 +00:00
6a401c9e52 fix: SeriesTime and SeriesDate are only set for the first series and undefined for the remaining ones (#2476)
* set SeriesDate and SeriesTime for all series, instead of only for the first series

* set SeriesDate and SeriesTime only when they are undefined

* fix: equality operator

Co-authored-by: Christian El Salloum <christian.elsalloum@emtensor.com>
Co-authored-by: Alireza <ar.sedghi@gmail.com>
2021-12-23 15:35:37 -05:00
Yo Yehudi 23376d279d chore: Add research notice (#2458) 2021-12-23 12:01:07 -05:00
ohif-bot 4110b0422c chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.2.16
2021-12-10 09:38:52 +00:00
Davide Punzo ed501bb462 IDC Re #2633: fix slider min and max (#2634)
* IDC Re #2633: fix slider min and max
2021-12-10 10:33:30 +01:00
Davide Punzo abf050e162 update docker remote version for CI (#2629) 2021-12-02 13:56:59 +01:00
ohif-bot 35ed82e161 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.12.3
 - @ohif/extension-debugging@0.3.1
 - @ohif/extension-dicom-html@1.3.14
 - @ohif/extension-dicom-pdf@1.1.1
 - @ohif/extension-dicom-rt@0.7.4
 - @ohif/extension-dicom-segmentation@0.7.5
 - @ohif/extension-dicom-tag-browser@0.2.15
 - @ohif/extension-vtk@1.12.7
 - @ohif/core@2.16.3
 - @ohif/viewer@4.12.7
2021-12-02 11:57:19 +00:00
Davide Punzo d0ccc22fd9 Update cornerstone deps (#2628)
* update cornerstone deps
2021-12-02 12:53:41 +01:00
ohif-bot 31841c495f chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.2.14
 - @ohif/extension-vtk@1.12.6
 - @ohif/core@2.16.2
 - @ohif/ui@1.10.5
 - @ohif/viewer@4.12.6
2021-11-30 14:20:06 +00:00
Davide Punzo 65505d9bce Re IDC #2604: show series (with unsupported modalities) on slides panel and on tag explorer (#2621)
* Re IDC #2604: show series (with unsupported modalities) on slides panel and on tag explorer
2021-11-30 15:15:51 +01:00
ohif-bot 460fdeb534 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.7.4
 - @ohif/viewer@4.12.5
2021-10-27 13:10:24 +00:00
Igor Octaviano f4b7091ae4 Merge pull request #2596 from OHIF/fix/ohif-2590
Re #2590: Fix segments visibility handler
2021-10-27 10:06:35 -03:00
Davide Punzo 1e72684455 Re #2590: Fix segments visibility handler 2021-10-26 13:00:01 +02:00
ohif-bot 5b0b6301b9 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.12.2
 - @ohif/extension-dicom-html@1.3.13
 - @ohif/extension-dicom-rt@0.7.3
 - @ohif/extension-dicom-segmentation@0.7.3
 - @ohif/extension-dicom-tag-browser@0.2.13
 - @ohif/extension-vtk@1.12.5
 - @ohif/core@2.16.1
 - @ohif/ui@1.10.4
 - @ohif/viewer@4.12.4
2021-10-26 08:23:03 +00:00
Igor OctavianoandDavide Punzo 03a34a75a1 Add checks for study metadata in rt and seg panels + bump dcmjs to fix circular references issues naturalizing datasets (#2588)
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2021-10-26 10:19:12 +02:00
ohif-bot 729272a985 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.3.12
 - @ohif/viewer@4.12.3
2021-10-25 15:11:00 +00:00
Bill Wallace 9194ec2404 fix(OHIF):The header depth was too deep for display SR files. (#2594) 2021-10-25 17:08:10 +02:00
ohif-bot 52c95a1209 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.4
 - @ohif/ui@1.10.3
 - @ohif/viewer@4.12.2
2021-10-15 11:37:07 +00:00
Davide Punzo 8903adaea7 Re #2571: thumbnail footer remove instance number and add link icon if any derived display set is present (#2582)
* Re #2571, thumbnail footer layout modifications: remove instance number and add link icon if any derived display set is present.
2021-10-15 13:29:36 +02:00
ohif-bot 9481e2a99e chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.7.2
 - @ohif/extension-dicom-segmentation@0.7.2
 - @ohif/viewer@4.12.1
2021-10-06 21:01:00 +00:00
Igor Octaviano f783fd7650 Merge pull request #2575 from OHIF/fix/ohif-2570
OHIF-2570: Update badge number based on active ds
2021-10-06 17:53:07 -03:00
Igor Octaviano 79afee7615 Merge branch 'master' into fix/ohif-2570 2021-10-05 09:40:49 -03:00
ohif-bot 539141fe5a chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.3
 - @ohif/core@2.16.0
 - @ohif/viewer@4.12.0
2021-10-04 07:36:45 +00:00
Bill Wallace a046c6c901 feat: Added support for WADO servers built with static hosting (e.g. S3) (#2566) 2021-10-04 09:30:36 +02:00
igoroctaviano 073be065cf Update badge number based on active ds 2021-10-01 18:09:48 -03:00
ohif-bot 252536a1e6 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.12.1
 - @ohif/extension-dicom-html@1.3.11
 - @ohif/extension-dicom-rt@0.7.1
 - @ohif/extension-dicom-segmentation@0.7.1
 - @ohif/extension-dicom-tag-browser@0.2.12
 - @ohif/extension-vtk@1.12.2
 - @ohif/core@2.15.1
 - @ohif/viewer@4.11.2
2021-09-28 12:14:17 +00:00
Davide Punzo 53b1d71d56 fix regression not showing warnings list on thumbnail for segmentations (#2563)
* fix regression not showing warnings list on thumbnail for segmentations

* update dcmjs version
2021-09-28 14:07:29 +02:00
Erik Ziegler 702d99cb93 Add FUNDING.yml 2021-09-27 14:23:37 +02:00
Alireza bb7b78bf33 chore(github): Create config.yml for issues (#2561)
* chore(github): Create config.yml for issues

* Delete ---support-question.md
2021-09-27 14:20:38 +02:00
ohif-bot b796fc9c62 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.12.1
 - @ohif/ui@1.10.2
 - @ohif/viewer@4.11.1
2021-09-24 11:04:33 +00:00
Erik Ziegler 66c587b139 fix: throttle thumbnail progress updates (#2557) 2021-09-24 12:58:30 +02:00
ohif-bot f6e2e58aad chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.12.0
 - @ohif/extension-debugging@0.3.0
 - @ohif/extension-dicom-pdf@1.1.0
 - @ohif/extension-dicom-rt@0.7.0
 - @ohif/extension-dicom-segmentation@0.7.0
 - @ohif/extension-vtk@1.12.0
 - @ohif/core@2.15.0
 - @ohif/viewer@4.11.0
2021-09-23 16:42:56 +00:00
Erik ZieglerandAlireza 489d4b0584 feat: Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 (#2537)
* feat: bump to prerelease of WADO Image Loader for testing

* switch to released versions

* update react-vtkjs-viewport and fix requestpool usage in StudyPrefetcher

* attempt to fix e2e tests for script-tag usage

* fix e2e test error

* switch to using webpack alias in PWA to use dynamic import version of OHIF

* fix copy plugin usage

* try to fix publicpath issue

* bump to 4.0.3

Co-authored-by: Alireza <ar.sedghi@gmail.com>
2021-09-23 18:37:29 +02:00
ohif-bot 36019156da chore(release): publish [skip ci]
- @ohif/extension-vtk@1.11.18
 - @ohif/core@2.14.1
 - @ohif/ui@1.10.1
 - @ohif/viewer@4.10.1
2021-09-13 13:10:07 +00:00
Igor Octaviano f2a371fc2d Display warning messages if fails to parse sr report (#2543) 2021-09-13 15:06:30 +02:00
ohif-bot ad2f038817 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.11.0
 - @ohif/extension-vtk@1.11.17
 - @ohif/core@2.14.0
 - @ohif/ui@1.10.0
 - @ohif/viewer@4.10.0
2021-09-09 08:02:24 +00:00
Igor OctavianoandAlireza 8f2604509d feat: OHIF-1000: Study prefetcher (#2466)
* Enable study prefetcher and expose configs

* Fix progress for all images

* Enable study loading listener

* Add new default for displaying progress bar for series

* Use events instead of redux to improve performance

* Add documentation, resolve issues with different datasets

* CR Updates

* CR Updates

* CR Updates & Resolve issue when activating both stack and study prefetch

* Add upward and update display set logic

* Add new behavior to prefetching orders

* Rollback display set check and activate cache check

* Remove prefetch check

* Check if displayset has images

* Debounce check cache events

* Remove usued debounce flags

* Ignore prefetching same image

* Use new image instead of image rendered

* Continue prefetch after cache check

* Update prefetch logic after checking cache

* Remove log

* Update displayset count default to 3

* Update netlify config

Co-authored-by: Alireza <ar.sedghi@gmail.com>
2021-09-09 09:58:24 +02:00
90 changed files with 3957 additions and 915 deletions

No files matched your search

+11 -7
View File
@@ -25,11 +25,9 @@ executors:
docker: docker:
# Primary container image where all steps run. # Primary container image where all steps run.
- image: 'cypress/browsers:node14.15.0-chrome86-ff82' - image: 'cypress/browsers:node14.15.0-chrome86-ff82'
- image: 'ohif/viewer-testdata:0.1-test'
defaults: &defaults defaults: &defaults
docker: docker:
- image: circleci/node:14.15.0 - image: cimg/node:14.18
environment: environment:
TERM: xterm # Enable colors in term TERM: xterm # Enable colors in term
QUICK_BUILD: true QUICK_BUILD: true
@@ -119,6 +117,7 @@ jobs:
command: yarn install --frozen-lockfile command: yarn install --frozen-lockfile
- setup_remote_docker: - setup_remote_docker:
version: 19.03.13
docker_layer_caching: false docker_layer_caching: false
- run: - run:
@@ -295,6 +294,7 @@ jobs:
- attach_workspace: - attach_workspace:
at: ~/repo at: ~/repo
- setup_remote_docker: - setup_remote_docker:
version: 19.03.13
docker_layer_caching: false docker_layer_caching: false
- run: - run:
name: Build and push Docker image name: Build and push Docker image
@@ -347,7 +347,8 @@ workflows:
store_artifacts: true store_artifacts: true
working_directory: platform/viewer working_directory: platform/viewer
build: build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js git submodule update --init &&
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
yarn run build yarn run build
start: yarn run test:e2e:serve start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*' spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
@@ -375,7 +376,8 @@ workflows:
store_artifacts: true store_artifacts: true
working_directory: platform/viewer working_directory: platform/viewer
build: build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js git submodule update --init &&
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
yarn run build:package yarn run build:package
start: yarn run test:e2e:serve start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*' spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
@@ -408,7 +410,8 @@ workflows:
store_artifacts: false store_artifacts: false
working_directory: platform/viewer working_directory: platform/viewer
build: build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js git submodule update --init &&
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
yarn run build yarn run build
# start server --> verify running --> percy + chrome + cypress # start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist command: yarn run test:e2e:dist
@@ -487,7 +490,8 @@ workflows:
store_artifacts: false store_artifacts: false
working_directory: platform/viewer working_directory: platform/viewer
build: build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js git submodule update --init &&
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
yarn run build yarn run build
# start server --> verify running --> percy + chrome + cypress # start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist command: yarn run test:e2e:dist
+1
View File
@@ -0,0 +1 @@
custom: https://giving.massgeneral.org/ohif
@@ -1,18 +0,0 @@
---
name: "\U0001F917 Support Question"
about: "I have a question \U0001F4AC"
title: ''
labels: 'Community: Question :question:, Triage :white_flag:'
assignees: ''
---
> :hand: We are a small team with limited resources. Your question is much more
> likely to be answered if it is
> [a good question](https://stackoverflow.com/help/how-to-ask)
**Description**
Questions can often be answered by our documentation. Unable to find an answer
in our docs? We'll try to help. In the meantime, if you answer your own
question, please respond with the answer here so that others may benefit as
well. Better yet, open a PR to expand our docs ^\_^
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 🤗 Support Question
url: https://community.ohif.org/
about: Please use our forum if you have questions or need help.
+3
View File
@@ -10,6 +10,9 @@ junit.xml
coverage/ coverage/
.docz/ .docz/
*.js.map *.js.map
.yarn
platform/docs/.docusaurus
platform/ui/storybook-static
# YALC (for Erik) # YALC (for Erik)
.yalc .yalc
+3
View File
@@ -0,0 +1,3 @@
[submodule "testdata"]
path = testdata
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
+1
View File
@@ -0,0 +1 @@
14.18.0
+1 -1
View File
@@ -28,7 +28,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
const config = { const config = {
mode: isProdBuild ? 'production' : 'development', mode: isProdBuild ? 'production' : 'development',
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map', devtool: isProdBuild ? 'source-map' : 'eval-cheap-module-source-map',
entry: { entry: {
app: `${SRC_DIR}/index.js`, app: `${SRC_DIR}/index.js`,
}, },
+11
View File
@@ -273,6 +273,17 @@ development of OHIF Viewer:
Leidos Biomedical Research under Task Order HHSN26100071 from NCI. Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized [IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
version of the OHIF Viewer. version of the OHIF Viewer.
### Research notice
Please note that this repository is participating in a study into sustainability
of open source projects. Data will be gathered about this repository for
approximately the next 12 months, starting from June 2021.
Data collected will include number of contributors, number of PRs, time taken to
close/merge these PRs, and issues closed.
For more information, please visit
[our informational page](https://sustainable-open-science-and-software.github.io/) or download our [participant information sheet](https://sustainable-open-science-and-software.github.io/assets/PIS_sustainable_software.pdf).
## License ## License
+39 -3
View File
@@ -67,6 +67,33 @@ window.config = {
extensions: [], extensions: [],
showStudyList: true, showStudyList: true,
filterQueryParam: false, filterQueryParam: false,
/**
* Disable caching of servers configuration.
*
* There will be no effect if you update the servers property of this config
* while your application is running because this property is cached in local storage.
*/
disableServersCache: false,
/**
* OHIF's study prefetcher configuration.
*
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
*/
studyPrefetcher: {
enabled: true,
order: 'all',
displaySetCount: 1,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: false,
includeActiveDisplaySet: true,
},
servers: { servers: {
dicomWeb: [ dicomWeb: [
{ {
@@ -81,7 +108,14 @@ window.config = {
}, },
], ],
}, },
// Supported Keys: https://craig.is/killing/mice /**
* Hotkey definitions.
* Supported Keys: https://craig.is/killing/mice
*
* @param {string} commandName
* @param {string} label
* @param {array} keys
*/
hotkeys: [ hotkeys: [
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
@@ -97,14 +131,16 @@ window.config = {
keys: ['v'], keys: ['v'],
}, },
], ],
/* Configuration passed to the bundled cornerstone extension /**
* Configuration passed to the bundled cornerstone extension
* *
* The cornerstone extension is currently tightly coupled to the platform. * The cornerstone extension is currently tightly coupled to the platform.
* Until we're able to decouple it, this key will serve as a workaround to * Until we're able to decouple it, this key will serve as a workaround to
* pass it configuration. * pass it configuration.
*
* @param {boolean} hideHandles Whether to show/hide annotation "handles"
*/ */
cornerstoneExtensionConfig: { cornerstoneExtensionConfig: {
/* Whether to show/hide annotation "handles" */
hideHandles: true, hideHandles: true,
}, },
}; };
+37
View File
@@ -0,0 +1,37 @@
# Viewer: Study Prefetcher & Stack Prefetch
Prefetching the stack of images of the active viewport is enabled by default for
cornerstone viewports via cornerstone tools's stack prefetch functionality.
In order to have a more customizable alternative for prefetching, OHIF provides
a built-in study prefetcher functionality which allows the user to set the order
in which the display sets are prefetched, the granularity, the timeout and
whether or not display a progress bar in the display set (close to the thumbnail
image) in the study browser.
You can customize these options via the viewer's configuration:
```js
window.config = {
/**
* OHIF's study prefetcher configuration.
*
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
*/
studyPrefetcher: {
enabled: true,
order: 'all',
displaySetCount: 1,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: false,
includeActiveDisplaySet: true,
},
};
```
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.12.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.3...@ohif/extension-cornerstone@2.12.4) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.2...@ohif/extension-cornerstone@2.12.3) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.12.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.1...@ohif/extension-cornerstone@2.12.2) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.0...@ohif/extension-cornerstone@2.12.1) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.11.0...@ohif/extension-cornerstone@2.12.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.10...@ohif/extension-cornerstone@2.11.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [2.10.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.9...@ohif/extension-cornerstone@2.10.10) (2021-07-26) ## [2.10.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.9...@ohif/extension-cornerstone@2.10.10) (2021-07-26)
+23 -2
View File
@@ -85,12 +85,32 @@ Tools can be configured through extension configuration using the tools key:
... ...
``` ```
## Stack prefetch configuration
Stack prefetch can be configured through extension configuration using the
stackPrefetch key:
```js
...
cornerstoneExtensionConfig: {
stackPrefetch: {
enabled: true,
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
},
},
...
```
## Annotate Tools Configuration ## Annotate Tools Configuration
*We currently support one property for annotation tools.* _We currently support one property for annotation tools._
### Hide handles ### Hide handles
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
This extension configuration allows you to toggle on/off handle rendering for
all annotate tools:
```js ```js
... ...
@@ -119,3 +139,4 @@ This extension configuration allows you to toggle on/off handle rendering for al
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools [cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone [cornerstone]: https://github.com/cornerstonejs/cornerstone
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
```
+8 -8
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.10.10", "version": "2.12.4",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -32,12 +32,12 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.9",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^4.1.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.11",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -51,6 +51,6 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "4.0.5" "react-cornerstone-viewport": "4.1.1"
} }
} }
@@ -53,7 +53,11 @@ const mapStateToProps = (state, ownProps) => {
// Currently justing using escape hatch + commands // Currently justing using escape hatch + commands
// activeTool: activeButton && activeButton.command, // activeTool: activeButton && activeButton.command,
...dataFromStore, ...dataFromStore,
isStackPrefetchEnabled: isActive, isStackPrefetchEnabled: ownProps.hasOwnProperty('isStackPrefetchEnabled')
? ownProps.isStackPrefetchEnabled
: ownProps.stackPrefetch
? ownProps.stackPrefetch.enabled
: isActive,
isPlaying, isPlaying,
frameRate, frameRate,
//stack: viewportSpecificData.stack, //stack: viewportSpecificData.stack,
@@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay' import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay';
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -15,6 +15,7 @@ class OHIFCornerstoneViewport extends Component {
static defaultProps = { static defaultProps = {
customProps: {}, customProps: {},
isStackPrefetchEnabled: true,
}; };
static propTypes = { static propTypes = {
@@ -23,6 +24,8 @@ class OHIFCornerstoneViewport extends Component {
viewportIndex: PropTypes.number, viewportIndex: PropTypes.number,
children: PropTypes.node, children: PropTypes.node,
customProps: PropTypes.object, customProps: PropTypes.object,
stackPrefetch: PropTypes.object,
isStackPrefetchEnabled: PropTypes.bool,
}; };
static id = 'OHIFCornerstoneViewport'; static id = 'OHIFCornerstoneViewport';
@@ -180,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
if ( if (
displaySet.displaySetInstanceUID !== displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID || prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex displaySet.frameIndex !== prevDisplaySet.frameIndex
) { ) {
@@ -232,7 +235,12 @@ class OHIFCornerstoneViewport extends Component {
}; };
const warningsOverlay = props => { const warningsOverlay = props => {
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} /> return (
<OHIFCornerstoneViewportOverlay
{...props}
inconsistencyWarnings={inconsistencyWarnings}
/>
);
}; };
return ( return (
@@ -244,10 +252,11 @@ class OHIFCornerstoneViewport extends Component {
onNewImageDebounced={newImageHandler} onNewImageDebounced={newImageHandler}
onNewImageDebounceTime={300} onNewImageDebounceTime={300}
viewportOverlayComponent={warningsOverlay} viewportOverlayComponent={warningsOverlay}
stackPrefetch={this.props.stackPrefetch}
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
// ~~ Connected (From REDUX) // ~~ Connected (From REDUX)
// frameRate={frameRate} // frameRate={frameRate}
// isPlaying={false} // isPlaying={false}
// isStackPrefetchEnabled={true}
// onElementEnabled={() => {}} // onElementEnabled={() => {}}
// setViewportActive{() => {}} // setViewportActive{() => {}}
{...this.props.customProps} {...this.props.customProps}
+11 -2
View File
@@ -36,7 +36,7 @@ export default {
preRegistration({ servicesManager, configuration = {} }) { preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration }); init({ servicesManager, configuration });
}, },
getViewportModule({ commandsManager }) { getViewportModule({ commandsManager, appConfig }) {
const ExtendedOHIFCornerstoneViewport = props => { const ExtendedOHIFCornerstoneViewport = props => {
/** /**
* TODO: This appears to be used to set the redux parameters for * TODO: This appears to be used to set the redux parameters for
@@ -48,8 +48,17 @@ export default {
jumpData.refreshViewports = false; jumpData.refreshViewports = false;
commandsManager.runCommand('jumpToImage', jumpData); commandsManager.runCommand('jumpToImage', jumpData);
}; };
const { studyPrefetcher } = appConfig;
const isStackPrefetchEnabled =
studyPrefetcher && !studyPrefetcher.enabled;
return ( return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} /> <OHIFCornerstoneViewport
{...props}
onNewImage={onNewImageHandler}
isStackPrefetchEnabled={isStackPrefetchEnabled}
/>
); );
}; };
+9 -2
View File
@@ -37,7 +37,14 @@ export default function init({ servicesManager, configuration }) {
} }
}; };
const { csToolsConfig } = configuration; const {
csToolsConfig,
stackPrefetch = {
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
},
} = configuration;
const metadataProvider = OHIF.cornerstone.metadataProvider; const metadataProvider = OHIF.cornerstone.metadataProvider;
cornerstone.metaData.addProvider( cornerstone.metaData.addProvider(
@@ -52,7 +59,7 @@ export default function init({ servicesManager, configuration }) {
autoResizeViewports: false, autoResizeViewports: false,
}; };
initCornerstoneTools(defaultCsToolsConfig); initCornerstoneTools({ ...defaultCsToolsConfig, ...stackPrefetch });
const toolsGroupedByType = { const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool], touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
@@ -37,8 +37,8 @@ export default function(configuration = {}) {
// Configure stack prefetch // Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({ cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity, maxImagesToPrefetch: configuration.maxImagesToPrefetch,
preserveExistingPool: false, preserveExistingPool: configuration.preserveExistingPool,
maxSimultaneousRequests: 20, maxSimultaneousRequests: configuration.maxSimultaneousRequests,
}); });
} }
+19
View File
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.3.0...@ohif/extension-debugging@0.3.1) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-debugging
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.2...@ohif/extension-debugging@0.3.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.1...@ohif/extension-debugging@0.2.2) (2021-06-03) ## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.1...@ohif/extension-debugging@0.2.2) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-debugging **Note:** Version bump only for package @ohif/extension-debugging
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-debugging", "name": "@ohif/extension-debugging",
"version": "0.2.2", "version": "0.3.1",
"description": "OHIF extension for debugging.", "description": "OHIF extension for debugging.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,12 +28,12 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.11"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"detect-browser": "5.1.1", "detect-browser": "5.1.1",
"dicomweb-client": "^0.8.1", "dicomweb-client": "^0.8.3",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"jszip": "^3.2.2" "jszip": "^3.2.2"
} }
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.14...@ohif/extension-dicom-html@1.3.15) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.13...@ohif/extension-dicom-html@1.3.14) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.12...@ohif/extension-dicom-html@1.3.13) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.11...@ohif/extension-dicom-html@1.3.12) (2021-10-25)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.10...@ohif/extension-dicom-html@1.3.11) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.3.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.9...@ohif/extension-dicom-html@1.3.10) (2021-06-02) ## [1.3.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.9...@ohif/extension-dicom-html@1.3.10) (2021-06-02)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.3.10", "version": "1.3.15",
"description": "OHIF extension for rendering structured reports to HTML", "description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -160,7 +160,7 @@ const getContentSequence = (data, level = 1) => {
const root = []; const root = [];
if (header) { if (header) {
const HeaderDynamicLevel = `h${level}`; const HeaderDynamicLevel = `h${Math.min(level, 6)}`;
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>); root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
} }
@@ -170,7 +170,7 @@ const getContentSequence = (data, level = 1) => {
if (key === '_meta') { if (key === '_meta') {
const HeaderDynamicLevel = `h3`; const HeaderDynamicLevel = `h3`;
root.push(<hr />); root.push(<hr key={root.length} />);
root.push( root.push(
<HeaderDynamicLevel key="Metadata"> <HeaderDynamicLevel key="Metadata">
DICOM File Meta Information DICOM File Meta Information
@@ -217,7 +217,7 @@ class DicomHtmlViewport extends Component {
}; };
componentDidMount() { componentDidMount() {
const dataSet = this.setContentFromByteArray(this.props.byteArray); this.setContentFromByteArray(this.props.byteArray);
} }
setContentFromByteArray(byteArray) { setContentFromByteArray(byteArray) {
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.1.1...@ohif/extension-dicom-pdf@1.1.2) (2022-01-29)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.1.0...@ohif/extension-dicom-pdf@1.1.1) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.6...@ohif/extension-dicom-pdf@1.1.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10) ## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-pdf **Note:** Version bump only for package @ohif/extension-dicom-pdf
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-pdf", "name": "@ohif/extension-dicom-pdf",
"version": "1.0.6", "version": "1.1.2",
"description": "OHIF extension for Dicom PDF", "description": "OHIF extension for Dicom PDF",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.11",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
+3 -1
View File
@@ -33,6 +33,7 @@ class DicomPDFViewport extends Component {
static propTypes = { static propTypes = {
byteArray: TypedArrayProp.uint8, byteArray: TypedArrayProp.uint8,
rawPdf: PropTypes.bool,
useNative: PropTypes.bool, useNative: PropTypes.bool,
viewportData: PropTypes.object, viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number, activeViewportIndex: PropTypes.number,
@@ -45,7 +46,8 @@ class DicomPDFViewport extends Component {
}; };
async componentDidMount() { async componentDidMount() {
const dataSet = this.parseByteArray(this.props.byteArray); const { rawPdf } = this.props
const dataSet = !rawPdf && this.parseByteArray(this.props.byteArray);
const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray); const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray);
this.setState(state => ({ ...state, fileURL })); this.setState(state => ({ ...state, fileURL }));
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import OHIFComponentPlugin from './OHIFComponentPlugin.js'; import OHIFComponentPlugin from './OHIFComponentPlugin.js';
import DicomPDFViewport from './DicomPDFViewport'; import DicomPDFViewport from './DicomPDFViewport';
import { str2ab } from '@ohif/core';
const { DicomLoaderService } = OHIF.utils; const { DicomLoaderService } = OHIF.utils;
@@ -18,6 +19,7 @@ class OHIFDicomPDFViewport extends Component {
state = { state = {
byteArray: null, byteArray: null,
rawPdf: false,
error: null, error: null,
}; };
@@ -34,6 +36,15 @@ class OHIFDicomPDFViewport extends Component {
componentDidMount() { componentDidMount() {
const { displaySet, studies } = this.props.viewportData; const { displaySet, studies } = this.props.viewportData;
if (displaySet.metadata && displaySet.metadata.EncapsulatedDocument) {
const { InlineBinary, BulkDataURI } = displaySet.metadata.EncapsulatedDocument;
if (InlineBinary) {
const inlineBinaryData = atob(InlineBinary);
const byteArray = str2ab(inlineBinaryData);
this.setState({ byteArray, rawPdf: true });
return;
}
}
DicomLoaderService.findDicomDataPromise(displaySet, studies).then( DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
data => this.setState({ byteArray: new Uint8Array(data) }), data => this.setState({ byteArray: new Uint8Array(data) }),
error => { error => {
@@ -49,15 +60,16 @@ class OHIFDicomPDFViewport extends Component {
viewportIndex, viewportIndex,
activeViewportIndex, activeViewportIndex,
} = this.props; } = this.props;
const { byteArray, error } = this.state; const { byteArray, error, rawPdf } = this.state;
const { id, init, destroy } = OHIFDicomPDFViewport; const { id, init, destroy } = OHIFDicomPDFViewport;
const pluginProps = { id, init, destroy }; const pluginProps = { id, init, destroy };
return ( return (
<OHIFComponentPlugin {...pluginProps}> <OHIFComponentPlugin {...pluginProps}>
{byteArray && ( {(byteArray) && (
<DicomPDFViewport <DicomPDFViewport
byteArray={byteArray} byteArray={byteArray}
rawPdf={rawPdf}
setViewportActive={setViewportActive} setViewportActive={setViewportActive}
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
activeViewportIndex={activeViewportIndex} activeViewportIndex={activeViewportIndex}
+59
View File
@@ -3,6 +3,65 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.5...@ohif/extension-dicom-rt@0.7.6) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.4...@ohif/extension-dicom-rt@0.7.5) (2022-01-19)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.3...@ohif/extension-dicom-rt@0.7.4) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.2...@ohif/extension-dicom-rt@0.7.3) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.1...@ohif/extension-dicom-rt@0.7.2) (2021-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.0...@ohif/extension-dicom-rt@0.7.1) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-dicom-rt
# [0.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.14...@ohif/extension-dicom-rt@0.7.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [0.6.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.13...@ohif/extension-dicom-rt@0.6.14) (2021-09-03) ## [0.6.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.13...@ohif/extension-dicom-rt@0.6.14) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt **Note:** Version bump only for package @ohif/extension-dicom-rt
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.6.14", "version": "0.7.6",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,9 +29,9 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"gl-matrix": "^3.3.0", "gl-matrix": "^3.3.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -81,13 +81,10 @@ const RTPanel = ({
useEffect(() => { useEffect(() => {
updateStructureSets(); updateStructureSets();
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets); document.addEventListener('rt-panel-tab-updated', updateStructureSets);
return () => { return () => {
document.removeEventListener( document.removeEventListener('rt-panel-tab-updated', updateStructureSets);
'extensiondicomrtrtloaded',
updateStructureSets
);
}; };
}, []); }, []);
+7 -8
View File
@@ -91,17 +91,12 @@ export default {
} }
if (activeViewport) { 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( const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID activeViewport.StudyInstanceUID
); );
if (!studyMetadata) {
return;
}
const referencedDisplaySets = studyMetadata.getDerivedDatasets({ const referencedDisplaySets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID, referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT', Modality: 'RTSTRUCT',
@@ -112,6 +107,10 @@ export default {
['RTSTRUCT'].includes(ds.Modality) ['RTSTRUCT'].includes(ds.Modality)
) )
) { ) {
triggerRTPanelUpdatedEvent({
badgeNumber: referencedDisplaySets.length,
target: 'rt-panel',
});
return false; return false;
} }
} }
@@ -3,6 +3,65 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.5...@ohif/extension-dicom-segmentation@0.7.6) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.4...@ohif/extension-dicom-segmentation@0.7.5) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.3...@ohif/extension-dicom-segmentation@0.7.4) (2021-10-27)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.2...@ohif/extension-dicom-segmentation@0.7.3) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.1...@ohif/extension-dicom-segmentation@0.7.2) (2021-10-06)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.0...@ohif/extension-dicom-segmentation@0.7.1) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.11...@ohif/extension-dicom-segmentation@0.7.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.10...@ohif/extension-dicom-segmentation@0.6.11) (2021-09-03) ## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.10...@ohif/extension-dicom-segmentation@0.6.11) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation **Note:** Version bump only for package @ohif/extension-dicom-segmentation
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.6.11", "version": "0.7.6",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,9 +29,9 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -24,6 +24,7 @@ const SegmentItem = ({
onClick, onClick,
itemClass, itemClass,
color, color,
labelmap3D,
visible, visible,
onVisibilityChange, onVisibilityChange,
}) => { }) => {
@@ -39,7 +40,7 @@ const SegmentItem = ({
event.stopPropagation(); event.stopPropagation();
const newVisibility = !isVisible; const newVisibility = !isVisible;
setIsVisible(newVisibility); setIsVisible(newVisibility);
onVisibilityChange(newVisibility, index); onVisibilityChange(newVisibility, index, labelmap3D);
}; };
return ( return (
@@ -431,23 +431,43 @@ const SegmentationPanel = ({
return enabledElements[activeIndex].element; return enabledElements[activeIndex].element;
}; };
const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => { const onSegmentVisibilityChangeHandler = (
/** Get all labelmaps with this segmentNumber (overlapping segments) */ isVisible,
const { labelmaps3D } = getBrushStackState(); segmentNumber,
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => { labelmap3D
return labelmaps2D.some(({ segmentsOnLabelmap }) => ) => {
segmentsOnLabelmap.includes(segmentNumber)
);
});
let segmentsHidden = []; let segmentsHidden = [];
possibleLabelMaps3D.forEach(labelmap3D => { if (labelmap3D.metadata.hasOverlapping) {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible; /** Get all labelmaps with this segmentNumber and that
* are from the same series (overlapping segments) */
const { labelmaps3D } = getBrushStackState();
segmentsHidden = [ const sameSeriesLabelMaps3D = labelmaps3D.filter(({ metadata }) => {
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]), return (
]; labelmap3D.metadata.segmentationSeriesInstanceUID ===
}); metadata.segmentationSeriesInstanceUID
);
});
const possibleLabelMaps3D = sameSeriesLabelMaps3D.filter(
({ labelmaps2D }) => {
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
segmentsOnLabelmap.includes(segmentNumber)
);
}
);
possibleLabelMaps3D.forEach(labelmap3D => {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
];
});
} else {
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
segmentsHidden = [...labelmap3D.segmentsHidden];
}
setState(state => ({ ...state, segmentsHidden })); setState(state => ({ ...state, segmentsHidden }));
@@ -514,6 +534,7 @@ const SegmentationPanel = ({
label={segmentLabel} label={segmentLabel}
index={segmentNumber} index={segmentNumber}
color={color} color={color}
labelmap3D={labelmap3D}
visible={!labelmap3D.segmentsHidden[segmentIndex]} visible={!labelmap3D.segmentsHidden[segmentIndex]}
onVisibilityChange={onSegmentVisibilityChangeHandler} onVisibilityChange={onSegmentVisibilityChangeHandler}
/> />
@@ -64,8 +64,17 @@ export default function getSopClassHandlerModule({ servicesManager }) {
metadata, metadata,
}; };
segDisplaySet.getSourceDisplaySet = function(studies, activateLabelMap = true, onDisplaySetLoadFailureHandler) { segDisplaySet.getSourceDisplaySet = function(
return getSourceDisplaySet(studies, segDisplaySet, activateLabelMap, onDisplaySetLoadFailureHandler); studies,
activateLabelMap = true,
onDisplaySetLoadFailureHandler
) {
return getSourceDisplaySet(
studies,
segDisplaySet,
activateLabelMap,
onDisplaySetLoadFailureHandler
);
}; };
segDisplaySet.load = async function(referencedDisplaySet, studies) { segDisplaySet.load = async function(referencedDisplaySet, studies) {
@@ -98,6 +107,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
if (labelmapBufferArray.length > 1) { if (labelmapBufferArray.length > 1) {
let labelmapIndexes = []; let labelmapIndexes = [];
for (let i = 0; i < labelmapBufferArray.length; ++i) { for (let i = 0; i < labelmapBufferArray.length; ++i) {
segMetadata.segmentationSeriesInstanceUID =
segDisplaySet.SeriesInstanceUID;
segMetadata.hasOverlapping = true;
labelmapIndexes.push( labelmapIndexes.push(
await loadSegmentation( await loadSegmentation(
imageIds, imageIds,
@@ -118,6 +130,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
labelmapIndex = labelmapIndexes[0]; labelmapIndex = labelmapIndexes[0];
console.warn('Overlapping segments!'); console.warn('Overlapping segments!');
} else { } else {
segMetadata.segmentationSeriesInstanceUID =
segDisplaySet.SeriesInstanceUID;
segMetadata.hasOverlapping = false;
labelmapIndex = await loadSegmentation( labelmapIndex = await loadSegmentation(
imageIds, imageIds,
segDisplaySet, segDisplaySet,
+18 -1
View File
@@ -126,7 +126,7 @@ export default {
label: 'Segmentations', label: 'Segmentations',
target: 'segmentation-panel', target: 'segmentation-panel',
stateEvent: SegmentationPanelTabUpdatedEvent, stateEvent: SegmentationPanelTabUpdatedEvent,
isDisabled: studies => { isDisabled: (studies, activeViewport) => {
if (!studies) { if (!studies) {
return true; return true;
} }
@@ -139,6 +139,23 @@ export default {
const series = study.series[j]; const series = study.series[j];
if (series.Modality === 'SEG') { if (series.Modality === 'SEG') {
if (activeViewport) {
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
if (!studyMetadata) {
return;
}
const referencedDS = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID:
activeViewport.SeriesInstanceUID,
Modality: 'SEG',
});
triggerSegmentationPanelTabUpdatedEvent({
badgeNumber: referencedDS.length,
target: 'segmentation-panel',
});
}
return false; return false;
} }
} }
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.16...@ohif/extension-dicom-tag-browser@0.2.17) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.15...@ohif/extension-dicom-tag-browser@0.2.16) (2021-12-10)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.14...@ohif/extension-dicom-tag-browser@0.2.15) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.13...@ohif/extension-dicom-tag-browser@0.2.14) (2021-11-30)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.12...@ohif/extension-dicom-tag-browser@0.2.13) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.11...@ohif/extension-dicom-tag-browser@0.2.12) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
## [0.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.10...@ohif/extension-dicom-tag-browser@0.2.11) (2021-06-03) ## [0.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.10...@ohif/extension-dicom-tag-browser@0.2.11) (2021-06-03)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser **Note:** Version bump only for package @ohif/extension-dicom-tag-browser
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-tag-browser", "name": "@ohif/extension-dicom-tag-browser",
"version": "0.2.11", "version": "0.2.17",
"description": "OHIF extension for checking DICOM headers.", "description": "OHIF extension for checking DICOM headers.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,12 +28,12 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"react": "^16.8.6" "react": "^16.8.6"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"dicomweb-client": "^0.8.1", "dicomweb-client": "^0.8.3",
"moment": "2.24.0", "moment": "2.24.0",
"react-select": "^3.0.8" "react-select": "^3.0.8"
} }
@@ -18,7 +18,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
activeDisplaySetInstanceUID, activeDisplaySetInstanceUID,
setActiveDisplaySetInstanceUID, setActiveDisplaySetInstanceUID,
] = useState(displaySetInstanceUID); ] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(0); const [activeInstance, setActiveInstance] = useState(1);
const [tags, setTags] = useState([]); const [tags, setTags] = useState([]);
const [meta, setMeta] = useState(''); const [meta, setMeta] = useState('');
const [instanceList, setInstanceList] = useState([]); const [instanceList, setInstanceList] = useState([]);
@@ -51,20 +51,21 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
description: displayDate, description: displayDate,
onClick: () => { onClick: () => {
setActiveDisplaySetInstanceUID(displaySetInstanceUID); setActiveDisplaySetInstanceUID(displaySetInstanceUID);
setActiveInstance(0); setActiveInstance(1);
}, },
}; };
}); });
let metadata; let metadata;
const isImageStack = activeDisplaySet instanceof ImageSet; const isImageStack =
activeDisplaySet instanceof ImageSet &&
activeDisplaySet.isModalitySupported === true;
let selectedInstanceValue;
let instanceList; let instanceList;
if (isImageStack) { if (isImageStack) {
const { images } = activeDisplaySet; const { images } = activeDisplaySet;
const image = images[activeInstance]; const image = images[activeInstance - 1];
instanceList = images.map((image, index) => { instanceList = images.map((image, index) => {
const metadata = image.getData().metadata; const metadata = image.getData().metadata;
@@ -91,7 +92,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
setInstanceList(instanceList); setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList); setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack); setIsImageStack(isImageStack);
}, [activeDisplaySetInstanceUID, activeInstance]); }, [activeDisplaySetInstanceUID, activeInstance, displaySets]);
const selectedDisplaySetValue = displaySetList.find( const selectedDisplaySetValue = displaySetList.find(
ds => ds.value === activeDisplaySetInstanceUID ds => ds.value === activeDisplaySetInstanceUID
@@ -106,7 +107,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
showValue showValue
step={1} step={1}
min={1} min={1}
max={instanceList.length - 1} max={instanceList.length}
value={activeInstance} value={activeInstance}
valueRenderer={value => <p>Instance Number: {value}</p>} valueRenderer={value => <p>Instance Number: {value}</p>}
onChange={({ target }) => { onChange={({ target }) => {
+123
View File
@@ -3,6 +3,129 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.11...@ohif/extension-vtk@1.12.12) (2022-01-29)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.10...@ohif/extension-vtk@1.12.11) (2022-01-20)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.9...@ohif/extension-vtk@1.12.10) (2022-01-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.8...@ohif/extension-vtk@1.12.9) (2021-12-23)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.7...@ohif/extension-vtk@1.12.8) (2021-12-23)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.6...@ohif/extension-vtk@1.12.7) (2021-12-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.5...@ohif/extension-vtk@1.12.6) (2021-11-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.4...@ohif/extension-vtk@1.12.5) (2021-10-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.3...@ohif/extension-vtk@1.12.4) (2021-10-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.2...@ohif/extension-vtk@1.12.3) (2021-10-04)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.1...@ohif/extension-vtk@1.12.2) (2021-09-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.12.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.0...@ohif/extension-vtk@1.12.1) (2021-09-24)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.12.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.18...@ohif/extension-vtk@1.12.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [1.11.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.17...@ohif/extension-vtk@1.11.18) (2021-09-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.16...@ohif/extension-vtk@1.11.17) (2021-09-09)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.15...@ohif/extension-vtk@1.11.16) (2021-09-03) ## [1.11.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.15...@ohif/extension-vtk@1.11.16) (2021-09-03)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+12 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.11.16", "version": "1.12.12",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -32,11 +32,11 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"@ohif/i18n": "^0.50.0", "@ohif/i18n": "^0.50.0",
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^4.1.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.11",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
@@ -50,14 +50,14 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-vtkjs-viewport": "^0.14.2" "react-vtkjs-viewport": "^0.14.4"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.13.15", "@ohif/core": "^2.16.8",
"@ohif/ui": "^1.9.3", "@ohif/ui": "^1.10.5",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^4.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.11",
"gh-pages": "^2.0.1", "gh-pages": "^2.0.1",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
+4 -2
View File
@@ -56,6 +56,7 @@
"@babel/preset-env": "^7.5.0", "@babel/preset-env": "^7.5.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@percy/cypress": "^2.2.0", "@percy/cypress": "^2.2.0",
"@webpack-cli/serve": "^1.6.1",
"babel-eslint": "9.x", "babel-eslint": "9.x",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-inline-react-svg": "1.1.0", "babel-plugin-inline-react-svg": "1.1.0",
@@ -104,8 +105,8 @@
"stylus-loader": "^3.0.2", "stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^2.1.0", "terser-webpack-plugin": "^2.1.0",
"webpack": "^4.35.2", "webpack": "^4.35.2",
"webpack-cli": "^3.3.5", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^3.7.2", "webpack-dev-server": "^4.7.3",
"webpack-hot-middleware": "^2.25.0", "webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.1", "webpack-merge": "^4.2.1",
"workbox-webpack-plugin": "^5.0.0-beta.1", "workbox-webpack-plugin": "^5.0.0-beta.1",
@@ -123,6 +124,7 @@
] ]
}, },
"resolutions": { "resolutions": {
"**/node-gyp": "8.4.1",
"**/@babel/runtime": "7.5.5" "**/@babel/runtime": "7.5.5"
} }
} }
+119
View File
@@ -3,6 +3,125 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.16.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.7...@ohif/core@2.16.8) (2022-01-29)
**Note:** Version bump only for package @ohif/core
## [2.16.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.6...@ohif/core@2.16.7) (2022-01-20)
**Note:** Version bump only for package @ohif/core
## [2.16.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.5...@ohif/core@2.16.6) (2022-01-13)
**Note:** Version bump only for package @ohif/core
## [2.16.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.4...@ohif/core@2.16.5) (2021-12-23)
### Bug Fixes
* DICOMFileLoadingListener typo in method name ([#2647](https://github.com/OHIF/Viewers/issues/2647)) ([761d54f](https://github.com/OHIF/Viewers/commit/761d54fbbfb376c20ee15794ef7bb7111f64a06f))
## [2.16.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.3...@ohif/core@2.16.4) (2021-12-23)
### Bug Fixes
* SeriesTime and SeriesDate are only set for the first series and undefined for the remaining ones ([#2476](https://github.com/OHIF/Viewers/issues/2476)) ([6a401c9](https://github.com/OHIF/Viewers/commit/6a401c9e5204cb4f7af6aa33190ad5a8d0879de6))
## [2.16.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.2...@ohif/core@2.16.3) (2021-12-02)
**Note:** Version bump only for package @ohif/core
## [2.16.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.1...@ohif/core@2.16.2) (2021-11-30)
**Note:** Version bump only for package @ohif/core
## [2.16.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.0...@ohif/core@2.16.1) (2021-10-26)
**Note:** Version bump only for package @ohif/core
# [2.16.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.15.1...@ohif/core@2.16.0) (2021-10-04)
### Features
* Added support for WADO servers built with static hosting (e.g. S3) ([#2566](https://github.com/OHIF/Viewers/issues/2566)) ([a046c6c](https://github.com/OHIF/Viewers/commit/a046c6c9017a84e5ead71d5113b5e892333b4130))
## [2.15.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.15.0...@ohif/core@2.15.1) (2021-09-28)
**Note:** Version bump only for package @ohif/core
# [2.15.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.14.1...@ohif/core@2.15.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [2.14.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.14.0...@ohif/core@2.14.1) (2021-09-13)
**Note:** Version bump only for package @ohif/core
# [2.14.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.15...@ohif/core@2.14.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [2.13.15](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.14...@ohif/core@2.13.15) (2021-09-03) ## [2.13.15](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.14...@ohif/core@2.13.15) (2021-09-03)
**Note:** Version bump only for package @ohif/core **Note:** Version bump only for package @ohif/core
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.13.15", "version": "2.16.8",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -31,16 +31,16 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage" "test:unit:ci": "jest --ci --runInBand --collectCoverage"
}, },
"peerDependencies": { "peerDependencies": {
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^4.1.0",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.11"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"dicomweb-client": "^0.8.1", "dicomweb-client": "^0.8.3",
"immer": "6.0.2", "immer": "6.0.2",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
@@ -1,7 +1,10 @@
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import classes from '../classes';
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid'; import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
const { LogManager } = classes;
/** /**
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData * Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
* measurementData format is a viewer specific format to be stored into the redux and consumed by other components * measurementData format is a viewer specific format to be stored into the redux and consumed by other components
@@ -20,9 +23,21 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
); );
const { MeasurementReport } = dcmjs.adapters.Cornerstone; const { MeasurementReport } = dcmjs.adapters.Cornerstone;
const storedMeasurementByToolType = MeasurementReport.generateToolState(
dataset let storedMeasurementByToolType;
); try {
storedMeasurementByToolType = MeasurementReport.generateToolState(dataset);
} catch (error) {
const seriesDescription = dataset.SeriesDescription || '';
LogManager.publish(LogManager.EVENTS.OnLog, {
title: `Failed to parse ${seriesDescription} measurement report`,
type: 'warning',
message: error.message || '',
notify: true,
});
return;
}
const measurementData = {}; const measurementData = {};
let measurementNumber = 0; let measurementNumber = 0;
+18
View File
@@ -0,0 +1,18 @@
import PubSub from './PubSub';
/** Log Events */
export const LogEvents = Object.freeze({
OnLog: 'onLog',
});
/**
* Log manager that implements pub/sub.
* This manager can be used to send logs across different packages
* using previously registered events.
*/
class LogManager extends PubSub {
EVENTS = LogEvents;
}
/** Singleton */
export default new LogManager();
+75
View File
@@ -0,0 +1,75 @@
const _subscriptions = Symbol('subscriptions');
const _lastSubscriptionId = Symbol('lastSubscriptionId');
/**
* Class to implement publish/subscribe pattern
*
* @class
* @classdesc Pub/sub mechanism
*/
export default class PubSub {
constructor() {
this[_subscriptions] = {};
this[_lastSubscriptionId] = 0;
}
/**
* Subscribe to event
*
* @param {string} eventName Event name
* @param {Function} callback Callback function
* @returns {void}
*/
subscribe(eventName, callback) {
if (eventName === undefined) {
throw new Error('Event name is required');
}
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
if (!this[_subscriptions].hasOwnProperty(eventName)) {
this[_subscriptions][eventName] = {};
}
const subscriptionId = `sub${this[_lastSubscriptionId]++}`;
this[_subscriptions][eventName][subscriptionId] = callback;
}
/**
* Removes a subscription
*
* @param {string} eventName Event name
* @param {Function} [callback] Callback function
* @returns {void}
*/
unsubscribe(eventName, callback) {
const callbacks = this[_subscriptions][eventName] || {};
for (let subscriptionId in callbacks) {
if (!callback) {
delete callbacks[subscriptionId];
} else if (callbacks[subscriptionId] === callback) {
delete callbacks[subscriptionId];
}
}
}
/**
* Publish event to all subscriptions
*
* @param {String} eventName Event name
* @param {any} [payload] Data that will be published
* @returns {void}
*/
publish(eventName, ...payload) {
if (eventName === undefined) {
throw new Error('Event name is required');
}
const callbacks = this[_subscriptions][eventName] || {};
for (let subscriptionId in callbacks) {
callbacks[subscriptionId](...payload);
}
}
}
+118 -31
View File
@@ -1,16 +1,14 @@
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import { import debounce from 'lodash.debounce';
clearStudyLoadingProgress,
setStudyLoadingProgress,
} from '../redux/actions';
import StackManager from '../utils/StackManager'; import StackManager from '../utils/StackManager';
import { StudyPrefetcher } from './StudyPrefetcher';
class BaseLoadingListener { class BaseLoadingListener {
constructor(stack, options = {}) { constructor(stack, options = {}) {
this.id = BaseLoadingListener.getNewId(); this.id = BaseLoadingListener.getNewId();
this.stack = stack; this.stack = stack;
this.startListening();
this.statsItemsLimit = options.statsItemsLimit || 2; this.statsItemsLimit = options.statsItemsLimit || 2;
this.stats = { this.stats = {
items: [], items: [],
@@ -60,6 +58,9 @@ class BaseLoadingListener {
} }
_getProgressId() { _getProgressId() {
/**
* TODO: The id key should be configurable.
*/
const displaySetInstanceUID = this.stack.displaySetInstanceUID; const displaySetInstanceUID = this.stack.displaySetInstanceUID;
return 'StackProgress:' + displaySetInstanceUID; return 'StackProgress:' + displaySetInstanceUID;
} }
@@ -96,11 +97,18 @@ class BaseLoadingListener {
class DICOMFileLoadingListener extends BaseLoadingListener { class DICOMFileLoadingListener extends BaseLoadingListener {
constructor(stack, options) { constructor(stack, options) {
super(stack, options); super(stack, options);
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandler.bind(
this
);
this._dataSetUrl = this._getDataSetUrl(stack); this._dataSetUrl = this._getDataSetUrl(stack);
this._lastLoaded = 0; this._lastLoaded = 0;
// Check how many instances has already been download (cached) // Check how many instances has already been download (cached)
this._checkCachedData(); this._checkCachedData();
this.startListening();
} }
_checkCachedData() { _checkCachedData() {
@@ -127,10 +135,6 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
startListening() { startListening() {
const imageLoadProgressEventName = this._getImageLoadProgressEventName(); const imageLoadProgressEventName = this._getImageLoadProgressEventName();
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
this
);
this.stopListening(); this.stopListening();
cornerstone.events.addEventListener( cornerstone.events.addEventListener(
@@ -200,17 +204,57 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
} }
} }
const StudyLoadingListenerEvents = {
OnProgress: 'StudyLoadingListenerEvents.OnProgress',
};
function promiseState(promise, callback) {
// Symbols and RegExps are never content-equal
var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/;
function notifyPendingOrResolved(value) {
if (value === uniqueValue) {
return callback('pending');
} else {
return callback('fulfilled');
}
}
function notifyRejected(reason) {
return callback('rejected');
}
var race = [promise, Promise.resolve(uniqueValue)];
Promise.race(race).then(notifyPendingOrResolved, notifyRejected);
}
class StackLoadingListener extends BaseLoadingListener { class StackLoadingListener extends BaseLoadingListener {
constructor(stack, options = {}) { constructor(stack, options = {}) {
options.statsItemsLimit = 20; options.statsItemsLimit = 20;
super(stack, options); super(stack, options);
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
this
);
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds); this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
this.framesStatus = this._createArray(stack.imageIds.length, false); this.framesStatus = this._createArray(stack.imageIds.length, false);
this.loadedCount = 0; this.loadedCount = 0;
// Check how many instances has already been download (cached) // Check how many instances has already been download (cached)
this._checkCachedData(); this._debouncedSetProgressData = debounce((...args) => {
this._setProgressData(...args);
/** After checking cache, continue prefetch */
const studyPrefetcher = StudyPrefetcher.getInstance();
studyPrefetcher.prefetch(studyPrefetcher.getElement());
}, 300);
const debounced = true;
this._checkCachedData(debounced);
this.startListening();
} }
_convertImageIdsArrayToMap(imageIds) { _convertImageIdsArrayToMap(imageIds) {
@@ -238,18 +282,37 @@ class StackLoadingListener extends BaseLoadingListener {
return array; return array;
} }
_checkCachedData() { /**
// const imageIds = this.stack.imageIds; * Check if image id is cached in cornerstone.
// TODO: No way to check status of Promise. *
/*for(let i = 0; i < imageIds.length; i++) { * @param {string} imageId
const imageId = imageIds[i]; * @returns
*/
isImageCached(imageId) {
const image = cornerstone.imageCache.imageCache[imageId];
return image && image.sizeInBytes;
}
const imagePromise = cornerstone.imageCache.getImageLoadObject(imageId).promise; _checkCachedData(debounced = false) {
const imageIds = this.stack.imageIds;
if (imagePromise && (imagePromise.state() === 'resolved')) { for (let i = 0; i < imageIds.length; i++) {
this._updateFrameStatus(imageId, true); const imageId = imageIds[i];
}
}*/ const imageObject = cornerstone.imageCache.getImageLoadObject(imageId);
if (this.isImageCached(imageId)) {
this._updateFrameStatus(imageId, true, debounced);
}
if (imageObject && imageObject.promise) {
promiseState(imageObject.promise, state => {
if (state === 'fulfilled') {
this._updateFrameStatus(imageId, true, debounced);
}
});
}
}
} }
_getImageLoadedEventName() { _getImageLoadedEventName() {
@@ -272,11 +335,6 @@ class StackLoadingListener extends BaseLoadingListener {
const imageLoadedEventName = this._getImageLoadedEventName(); const imageLoadedEventName = this._getImageLoadedEventName();
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName(); const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
this
);
this.stopListening(); this.stopListening();
cornerstone.events.addEventListener( cornerstone.events.addEventListener(
@@ -303,7 +361,7 @@ class StackLoadingListener extends BaseLoadingListener {
); );
} }
_updateFrameStatus(imageId, loaded) { _updateFrameStatus(imageId, loaded, debounced) {
const imageData = this.imageDataMap.get(imageId); const imageData = this.imageDataMap.get(imageId);
if (!imageData || imageData.loaded === loaded) { if (!imageData || imageData.loaded === loaded) {
@@ -318,7 +376,7 @@ class StackLoadingListener extends BaseLoadingListener {
imageData.loaded = loaded; imageData.loaded = loaded;
this.framesStatus[imageData.index] = loaded; this.framesStatus[imageData.index] = loaded;
this.loadedCount += loaded ? 1 : -1; this.loadedCount += loaded ? 1 : -1;
this._updateProgress(); this._updateProgress(debounced);
} }
_setProgressData(progressId, progressData) { _setProgressData(progressId, progressData) {
@@ -339,7 +397,7 @@ class StackLoadingListener extends BaseLoadingListener {
); );
} }
_updateProgress() { _updateProgress(debounced) {
const totalFramesCount = this.stack.imageIds.length; const totalFramesCount = this.stack.imageIds.length;
const loadedFramesCount = this.loadedCount; const loadedFramesCount = this.loadedCount;
const loadingFramesCount = totalFramesCount - loadedFramesCount; const loadingFramesCount = totalFramesCount - loadedFramesCount;
@@ -357,6 +415,11 @@ class StackLoadingListener extends BaseLoadingListener {
framesStatus: this.framesStatus, framesStatus: this.framesStatus,
}; };
if (debounced) {
this._debouncedSetProgressData(progressId, progressData);
return;
}
this._setProgressData(progressId, progressData); this._setProgressData(progressId, progressData);
} }
@@ -371,11 +434,13 @@ class StackLoadingListener extends BaseLoadingListener {
} }
progressBar += ']'; progressBar += ']';
log.info(`${displaySetInstanceUID}: ${progressBar}`); console.info(`${displaySetInstanceUID}: ${progressBar}`);
} }
} }
class StudyLoadingListener { class StudyLoadingListener {
static events = StudyLoadingListenerEvents;
constructor(options) { constructor(options) {
this.listeners = {}; this.listeners = {};
this.options = options; this.options = options;
@@ -454,14 +519,36 @@ class StudyLoadingListener {
_getSchema(stack) { _getSchema(stack) {
const imageId = stack.imageIds[0]; const imageId = stack.imageIds[0];
if (!imageId) {
return;
}
const colonIndex = imageId.indexOf(':'); const colonIndex = imageId.indexOf(':');
return imageId.substring(0, colonIndex); return imageId.substring(0, colonIndex);
} }
// Singleton
static getInstance(options) { static getInstance(options) {
/**
* TODO: Use a different alternative without the use of events.
*/
const DEFAULT_OPTIONS = {
_setProgressData: (progressId, progressData) => {
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
detail: { progressId, progressData },
});
document.dispatchEvent(event);
},
_clearProgressById: progressId => {
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
detail: { progressId, percentComplete: 0 },
});
document.dispatchEvent(event);
},
};
if (!StudyLoadingListener._instance) { if (!StudyLoadingListener._instance) {
StudyLoadingListener._instance = new StudyLoadingListener(options); StudyLoadingListener._instance = new StudyLoadingListener(
options || DEFAULT_OPTIONS
);
} }
return StudyLoadingListener._instance; return StudyLoadingListener._instance;
+335 -56
View File
@@ -1,14 +1,27 @@
import log from '../log.js';
import OHIFError from './OHIFError';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import getImageId from '../utils/getImageId.js'; import getImageId from '../utils/getImageId.js';
const noop = () => {};
export class StudyPrefetcher { export class StudyPrefetcher {
constructor(studies) { options = {
order: 'closest',
displaySetCount: 1,
onImageCached: noop,
requestType: 'prefetch',
preventCache: false,
prefetchDisplaySetsTimeout: 300,
maxNumPrefetchRequests: 100,
includeActiveDisplaySet: false,
};
constructor(studies, options) {
this.studies = studies || []; this.studies = studies || [];
this.prefetchDisplaySetsTimeout = 300;
this.lastActiveViewportElement = null; if (options) {
this.options = { ...this.options, ...options };
this.options.requestType = 'prefetch';
}
cornerstone.events.addEventListener( cornerstone.events.addEventListener(
'cornerstoneimagecachefull.StudyPrefetcher', 'cornerstoneimagecachefull.StudyPrefetcher',
@@ -16,6 +29,9 @@ export class StudyPrefetcher {
); );
} }
/**
* Remove previously set event listeners and stop prefetching.
*/
destroy() { destroy() {
this.stopPrefetching(); this.stopPrefetching();
cornerstone.events.removeEventListener( cornerstone.events.removeEventListener(
@@ -24,95 +40,180 @@ export class StudyPrefetcher {
); );
} }
static getInstance() { /**
* Get StudyPrefetcher singleton instance.
*
* @param {array} studies
* @param {object} options
* @returns
*/
static getInstance(studies = [], options) {
if (!StudyPrefetcher.instance) { if (!StudyPrefetcher.instance) {
StudyPrefetcher.instance = new StudyPrefetcher([]); StudyPrefetcher.instance = new StudyPrefetcher(studies, options);
}
if (options) {
this.options = { ...this.options, ...options };
this.options.requestType = 'prefetch';
} }
return StudyPrefetcher.instance; return StudyPrefetcher.instance;
} }
/**
* OHIF study metadata instances.
*
* @param {array} studies
*/
setStudies(studies) { setStudies(studies) {
this.stopPrefetching(); this.stopPrefetching();
this.studies = studies; this.studies = studies;
} }
prefetch() { /**
* Get previously prefetched element.
*
* @returns {HTMLElement} Previously prefetched element.
*/
getElement() {
return this.element;
}
/**
* Prefetch related display sets based on cornerstone viewport element
* with previously set options.
*
* @param {*} element
* @param {string} displaySetInstanceUID the display set instance uid
* @returns
*/
prefetch(element, displaySetInstanceUID) {
if (!this.studies || !this.studies.length) { if (!this.studies || !this.studies.length) {
return; return;
} }
this.element = element;
try {
this.enabledElement = cornerstone.getEnabledElement(element);
} catch {
throw new Error('Failed to find the enabled element');
}
this.stopPrefetching(); this.stopPrefetching();
this.prefetchDisplaySets(); this.prefetchDisplaySets(displaySetInstanceUID);
} }
/**
* Stop prefetching images.
*/
stopPrefetching() { stopPrefetching() {
cornerstoneTools.requestPoolManager.clearRequestStack('prefetch'); cornerstone.imageLoadPoolManager.clearRequestStack('prefetch');
} }
prefetchDisplaySetsAsync(timeout) { /**
timeout = timeout || this.prefetchDisplaySetsTimeout; * Prefetch display sets async.
*
* @param {HTMLElement} element cornerstone viewport element
* @param {number} timeout
*/
prefetchDisplaySetsAsync(element, timeout) {
try {
this.enabledElement = cornerstone.getEnabledElement(element);
} catch {
throw new Error('Failed to find the enabled element');
}
timeout = timeout || this.options.prefetchDisplaySetsTimeout;
clearTimeout(this.prefetchDisplaySetsHandler); clearTimeout(this.prefetchDisplaySetsHandler);
this.prefetchDisplaySetsHandler = setTimeout(() => { this.prefetchDisplaySetsHandler = setTimeout(() => {
this.prefetchDisplaySets(); this.prefetchDisplaySets(element);
}, timeout); }, timeout);
} }
prefetchDisplaySets() { /**
// TODO: Allow passing in config * Extract all image ids from all display sets to be fetched and
let config = { * call method to add images to request pool manager.
order: 'closest', *
displaySetCount: 1, * @param {string} displaySetInstanceUID the display set instance uid
}; */
prefetchDisplaySets(displaySetInstanceUID) {
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config); const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(
displaySetInstanceUID
);
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch); const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
this.prefetchImageIds(imageIds); this.prefetchImageIds(imageIds);
} }
/**
* Add image ids to request pool manager.
*
* @param {array} imageIds
*/
prefetchImageIds(imageIds) { prefetchImageIds(imageIds) {
const nonCachedImageIds = this.filterCachedImageIds(imageIds); const nonCachedImageIds = this.filterCachedImageIds(imageIds);
const requestPoolManager = cornerstoneTools.requestPoolManager; const imageLoadPoolManager = cornerstone.imageLoadPoolManager;
const requestType = 'prefetch';
const preventCache = false; imageLoadPoolManager.maxNumRequests = {
const noop = () => {}; ...imageLoadPoolManager.maxNumRequests,
prefetch: this.options.maxNumPrefetchRequests,
};
let requestFn;
if (this.options.preventCache) {
requestFn = id => cornerstone.loadImage(id);
} else {
requestFn = id => cornerstone.loadAndCacheImage(id);
}
nonCachedImageIds.forEach(imageId => { nonCachedImageIds.forEach(imageId => {
requestPoolManager.addRequest( imageLoadPoolManager.addRequest(
{}, requestFn.bind(this, imageId),
imageId, this.options.requestType,
requestType, {
preventCache, imageId,
noop, }
noop
); );
}); });
requestPoolManager.startGrabbing();
} }
/**
* Get study by cornerstone image instance.
*
* @param {object} image
* @returns
*/
getStudy(image) { getStudy(image) {
const StudyInstanceUID = cornerstone.metaData.get( const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID', 'StudyInstanceUID',
image.imageId image.imageId
); );
return OHIF.viewer.Studies.find( const studies = this.studies;
study => study.StudyInstanceUID === StudyInstanceUID return studies.find(
study => study.getData().StudyInstanceUID === StudyInstanceUID
); );
} }
/**
* Get study series by cornerstone image instance.
*
* @param {object} study OHIF study instance
* @param {object} image cornerstone image instance object
* @returns
*/
getSeries(study, image) { getSeries(study, image) {
const SeriesInstanceUID = cornerstone.metaData.get( const SeriesInstanceUID = cornerstone.metaData.get(
'SeriesInstanceUID', 'SeriesInstanceUID',
image.imageId image.imageId
); );
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study); return study.getSeriesByUID(SeriesInstanceUID);
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
} }
/**
* Get sop instance by cornerstone image instance.
*
* @param {array} series
* @param {object} image
* @returns
*/
getInstance(series, image) { getInstance(series, image) {
const instanceMetadata = cornerstone.metaData.get( const instanceMetadata = cornerstone.metaData.get(
'instance', 'instance',
@@ -121,7 +222,33 @@ export class StudyPrefetcher {
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID); return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
} }
getActiveDisplaySet(displaySets, instance) { /**
* Returns the display set with given uid.
*
* @param {string} displaySetInstanceUID the display set instance uid
* @returns {object} displaySet
*/
getDisplaySetByUID(displaySetInstanceUID) {
let displaySet;
this.studies.forEach(study => {
const ds = study.displaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
if (ds) {
displaySet = ds;
}
});
return displaySet;
}
/**
* Get display set by SOPInstanceUID.
*
* @param {array} displaySets
* @param {object} instance
* @returns
*/
getDisplaySetBySOPInstanceUID(displaySets, instance) {
return displaySets.find(displaySet => { return displaySets.find(displaySet => {
return displaySet.images.some(displaySetImage => { return displaySet.images.some(displaySetImage => {
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID; return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
@@ -129,25 +256,48 @@ export class StudyPrefetcher {
}); });
} }
getDisplaySetsToPrefetch(config) { /**
* Get active viewport image based on cornerstone viewport element.
* @returns
*/
getActiveViewportImage() {
if (!this.enabledElement) {
return;
}
return this.enabledElement.image;
}
/**
* Prefetch display sets based on cornerstone viewport element image.
*
* @param {string} displaySetInstanceUID the display set instance uid
* @returns {array} displaySets
*/
getDisplaySetsToPrefetch(displaySetInstanceUID) {
const image = this.getActiveViewportImage(); const image = this.getActiveViewportImage();
if (!image || !config || !config.displaySetCount) { if (!image) {
return []; return [];
} }
/*const study = this.getStudy(image); const study = this.getStudy(image);
const series = this.getSeries(study, image); const series = this.getSeries(study, image);
const instance = this.getInstance(series, image);*/ const instance = this.getInstance(series, image);
const displaySets = study.displaySets; const displaySets = study.displaySets;
const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance); const activeDisplaySet = displaySetInstanceUID
? this.getDisplaySetByUID(displaySetInstanceUID)
: this.getDisplaySetBySOPInstanceUID(displaySets, instance);
const prefetchMethodMap = { const prefetchMethodMap = {
topdown: 'getFirstDisplaySets', topdown: 'getFirstDisplaySets',
downward: 'getNextDisplaySets', downward: 'getNextDisplaySets',
upward: 'getPreviousDisplaySets',
closest: 'getClosestDisplaySets', closest: 'getClosestDisplaySets',
all: 'getAllDisplaySets',
}; };
const prefetchOrder = config.order; const prefetchOrder = this.options.order;
const methodName = prefetchMethodMap[prefetchOrder]; const methodName = prefetchMethodMap[prefetchOrder];
const getDisplaySets = this[methodName]; const getDisplaySets = this[methodName];
@@ -163,18 +313,59 @@ export class StudyPrefetcher {
this, this,
displaySets, displaySets,
activeDisplaySet, activeDisplaySet,
config.displaySetCount this.options.displaySetCount,
this.options.includeActiveDisplaySet
); );
} }
getFirstDisplaySets(displaySets, activeDisplaySet, displaySetCount) { /**
* Get all display sets.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getAllDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const length = displaySets.length;
const selectedDisplaySets = [];
for (let i = 0; i < length; i++) {
const displaySet = displaySets[i];
selectedDisplaySets.push(displaySet);
}
return selectedDisplaySets;
}
/**
* Get all display sets in order after the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getFirstDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const length = displaySets.length; const length = displaySets.length;
const selectedDisplaySets = []; const selectedDisplaySets = [];
for (let i = 0; i < length && displaySetCount; i++) { for (let i = 0; i < length && displaySetCount; i++) {
const displaySet = displaySets[i]; const displaySet = displaySets[i];
if (displaySet !== activeDisplaySet) { if (includeActiveDisplaySet || displaySet !== activeDisplaySet) {
selectedDisplaySets.push(displaySet); selectedDisplaySets.push(displaySet);
displaySetCount--; displaySetCount--;
} }
@@ -183,21 +374,78 @@ export class StudyPrefetcher {
return selectedDisplaySets; return selectedDisplaySets;
} }
getNextDisplaySets(displaySets, activeDisplaySet, displaySetCount) { /**
* Get all display sets before the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getPreviousDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet); const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const begin = activeDisplaySetIndex + 1; const end = includeActiveDisplaySet
const end = Math.min(begin + displaySetCount, displaySets.length); ? activeDisplaySetIndex + 1
: activeDisplaySetIndex;
const previousDisplaySets = displaySets.slice(0, end);
return previousDisplaySets.reverse().slice(0, displaySetCount);
}
/**
* Get all display sets after the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getNextDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const begin = includeActiveDisplaySet
? activeDisplaySetIndex
: activeDisplaySetIndex + 1;
const end = Math.min(begin + displaySetCount, displaySets.length);
return displaySets.slice(begin, end); return displaySets.slice(begin, end);
} }
getClosestDisplaySets(displaySets, activeDisplaySet, displaySetCount) { /**
* Get all display set closest to the active display set.
*
* @param {array} displaySets
* @param {object} activeDisplaySet
* @param {number} displaySetCount
* @param {boolean} includeActiveDisplaySet
* @returns
*/
getClosestDisplaySets(
displaySets,
activeDisplaySet,
displaySetCount,
includeActiveDisplaySet
) {
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet); const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
const length = displaySets.length; const length = displaySets.length;
const selectedDisplaySets = []; const selectedDisplaySets = [];
let left = activeDisplaySetIndex - 1; let left = activeDisplaySetIndex - 1;
let right = activeDisplaySetIndex + 1; let right = activeDisplaySetIndex + 1;
if (includeActiveDisplaySet) {
selectedDisplaySets.push(displaySets[activeDisplaySetIndex]);
displaySetCount--;
}
while ((left >= 0 || right < length) && displaySetCount) { while ((left >= 0 || right < length) && displaySetCount) {
if (left >= 0) { if (left >= 0) {
selectedDisplaySets.push(displaySets[left]); selectedDisplaySets.push(displaySets[left]);
@@ -215,6 +463,12 @@ export class StudyPrefetcher {
return selectedDisplaySets; return selectedDisplaySets;
} }
/**
* Get all image ids from display sets.
*
* @param {array} displaySets
* @returns {array} image ids
*/
getImageIdsFromDisplaySets(displaySets) { getImageIdsFromDisplaySets(displaySets) {
let imageIds = []; let imageIds = [];
@@ -225,9 +479,19 @@ export class StudyPrefetcher {
return imageIds; return imageIds;
} }
/**
* Get all image ids from a given display set.
*
* @param {array} displaySet
* @returns
*/
getImageIdsFromDisplaySet(displaySet) { getImageIdsFromDisplaySet(displaySet) {
const imageIds = []; const imageIds = [];
if (!displaySet.images || displaySet.images.length < 1) {
return [];
}
// TODO: This duplicates work done by the stack manager // TODO: This duplicates work done by the stack manager
displaySet.images.forEach(image => { displaySet.images.forEach(image => {
const numFrames = image.numFrames; const numFrames = image.numFrames;
@@ -245,15 +509,30 @@ export class StudyPrefetcher {
return imageIds; return imageIds;
} }
/**
* Filter cached image ids from a set of image ids.
*
* @param {array} imageIds
* @returns {array} images not cached
*/
filterCachedImageIds(imageIds) { filterCachedImageIds(imageIds) {
return imageIds.filter(imageId => !this.isImageCached(imageId)); return imageIds.filter(imageId => !this.isImageCached(imageId));
} }
/**
* Check if image id is cached in cornerstone.
*
* @param {string} imageId
* @returns
*/
isImageCached(imageId) { isImageCached(imageId) {
const image = cornerstone.imageCache.imageCache[imageId]; const image = cornerstone.imageCache.imageCache[imageId];
return image && image.sizeInBytes; return image && image.sizeInBytes;
} }
/**
* Warns that cache is full and stops prefetching.
*/
cacheFullHandler = () => { cacheFullHandler = () => {
log.warn('Cache full'); log.warn('Cache full');
this.stopPrefetching(); this.stopPrefetching();
+4
View File
@@ -4,6 +4,8 @@ import CommandsManager from './CommandsManager.js';
import { DICOMFileLoadingListener } from './StudyLoadingListener'; import { DICOMFileLoadingListener } from './StudyLoadingListener';
import HotkeysManager from './HotkeysManager.js'; import HotkeysManager from './HotkeysManager.js';
import ImageSet from './ImageSet'; import ImageSet from './ImageSet';
import LogManager from './LogManager';
import PubSub from './PubSub';
import MetadataProvider from './MetadataProvider'; import MetadataProvider from './MetadataProvider';
import OHIFError from './OHIFError.js'; import OHIFError from './OHIFError.js';
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource'; import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
@@ -36,7 +38,9 @@ const classes = {
MetadataProvider, MetadataProvider,
CommandsManager, CommandsManager,
HotkeysManager, HotkeysManager,
LogManager,
ImageSet, ImageSet,
PubSub,
StudyPrefetcher, StudyPrefetcher,
StudyLoadingListener, StudyLoadingListener,
StackLoadingListener, StackLoadingListener,
@@ -9,7 +9,10 @@ import { SeriesMetadata } from './SeriesMetadata';
import { api } from 'dicomweb-client'; import { api } from 'dicomweb-client';
// - createStacks // - createStacks
import { isImage } from '../../utils/isImage'; import { isImage } from '../../utils/isImage';
import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable'; import {
isDisplaySetReconstructable,
isSpacingUniform,
} from '../../utils/isDisplaySetReconstructable';
import errorHandler from '../../errorHandler'; import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality'; import isLowPriorityModality from '../../utils/isLowPriorityModality';
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook'; import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
@@ -159,41 +162,64 @@ class StudyMetadata extends Metadata {
// series into another display set. // series into another display set.
const stackableInstances = []; const stackableInstances = [];
series.forEachInstance(instance => { series.forEachInstance(instance => {
let displaySet;
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010) // All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
if ( if (
!isImage(instance.getTagValue('SOPClassUID')) && !isImage(instance.getTagValue('SOPClassUID')) &&
!instance.getTagValue('Rows') !instance.getTagValue('Rows')
) { ) {
return; // we set an empty display and we add a isModalitySupported variable to
} // print a warning that the modality is not supported in the thumbnail.
const displaySet = new ImageSet([]);
let displaySet; const seriesData = series.getData();
if (isMultiFrame(instance)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({ displaySet.setAttributes({
sopClassUIDs, displaySetInstanceUID: displaySet.uid,
isClip: true, SeriesDate: seriesData.SeriesDate,
SeriesTime: seriesData.SeriesTime,
SeriesInstanceUID: series.getSeriesInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes SeriesNumber: instance.getTagValue('SeriesNumber'),
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length SeriesDescription: instance.getTagValue('SeriesDescription'),
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number numImageFrames: instance.getTagValue('NumberOfFrames'),
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime frameRate: instance.getTagValue('FrameTime'),
}); Modality: instance.getTagValue('Modality'),
displaySets.push(displaySet); isMultiFrame: false,
} else if (isSingleImageModality(instance.Modality)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
sopClassUIDs,
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
SeriesInstanceUID: series.getSeriesInstanceUID(),
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
isReconstructable: false,
isModalitySupported: false,
metadata: instance.getData().metadata,
}); });
displaySets.push(displaySet); displaySets.push(displaySet);
} else { } else {
stackableInstances.push(instance); if (isMultiFrame(instance)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
sopClassUIDs,
isClip: true,
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
});
displaySets.push(displaySet);
} else if (isSingleImageModality(instance.Modality)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
sopClassUIDs,
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
SeriesInstanceUID: series.getSeriesInstanceUID(),
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
});
displaySets.push(displaySet);
} else {
stackableInstances.push(instance);
}
} }
}); });
@@ -227,7 +253,6 @@ class StudyMetadata extends Metadata {
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet)); displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
} }
/** /**
* Returns the source display set of the derivated display set. * Returns the source display set of the derivated display set.
* @param {object} derivatedDisplaySet * @param {object} derivatedDisplaySet
@@ -242,40 +267,45 @@ class StudyMetadata extends Metadata {
}); });
const otherDisplaySets = allDisplaySets.filter( const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID ds =>
ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
); );
const { metadata } = derivatedDisplaySet; const { metadata } = derivatedDisplaySet;
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
(metadata, otherDisplaySets); metadata,
otherDisplaySets
);
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs || let noReferencedSeriesAvailable =
!referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0; referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) { if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs = referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence metadata
(metadata); );
} }
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs || noReferencedSeriesAvailable =
!referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0; referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) { if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs = referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence metadata,
(metadata, otherDisplaySets); otherDisplaySets
);
} }
const referencedSeriesAvailable = referencedSeriesInstanceUIDs && const referencedSeriesAvailable =
referencedSeriesInstanceUIDs.length !== 0; referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0;
if (referencedSeriesAvailable) { if (referencedSeriesAvailable) {
const referencedDisplaySet = otherDisplaySets.find(ds => const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID) referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
); );
;
return referencedDisplaySet; return referencedDisplaySet;
} }
}; }
/** /**
* Returns a list of derived datasets in the study, filtered by the given filter. * Returns a list of derived datasets in the study, filtered by the given filter.
@@ -303,7 +333,10 @@ class StudyMetadata extends Metadata {
if (referencedSeriesInstanceUID) { if (referencedSeriesInstanceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter( filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => { displaySet => {
return StudyMetadata.getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID; return (
StudyMetadata.getReferencedDisplaySet(displaySet, [this])
.SeriesInstanceUID === referencedSeriesInstanceUID
);
} }
); );
} }
@@ -311,8 +344,7 @@ class StudyMetadata extends Metadata {
if (referencedFrameOfReferenceUID) { if (referencedFrameOfReferenceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter( filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet =>
displaySet.FrameOfReferenceUID === displaySet.FrameOfReferenceUID === referencedFrameOfReferenceUID
referencedFrameOfReferenceUID
); );
} }
@@ -497,6 +529,14 @@ class StudyMetadata extends Metadata {
} }
} }
if (
this._displaySets.some(
ds => ds.displaySetInstanceUID === displaySet.displaySetInstanceUID
)
) {
return;
}
this._displaySets.splice(insertIndex, 0, displaySet); this._displaySets.splice(insertIndex, 0, displaySet);
this.displaySets = this._displaySets; this.displaySets = this._displaySets;
} }
@@ -861,8 +901,9 @@ const makeDisplaySet = (series, instances) => {
imageSet.sortByImagePositionPatient(); imageSet.sortByImagePositionPatient();
// check if the spacing is uniform and update isReconstructable // check if the spacing is uniform and update isReconstructable
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find(
(issue => issue === ReconstructionIssues.DATASET_4D); issue => issue === ReconstructionIssues.DATASET_4D
);
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D); displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
imageSet.isReconstructable = displaySpacingInfo.isUniform; imageSet.isReconstructable = displaySpacingInfo.isUniform;
@@ -875,11 +916,15 @@ const makeDisplaySet = (series, instances) => {
if (!imageSet.displayReconstructableInfo) { if (!imageSet.displayReconstructableInfo) {
// It is not reconstrabale Save type of warning // It is not reconstrabale Save type of warning
imageSet.reconstructionIssues = displaySpacingInfo ? imageSet.reconstructionIssues = displaySpacingInfo
displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) : ? displayReconstructableInfo.reconstructionIssues.concat(
displayReconstructableInfo.reconstructionIssues; displaySpacingInfo.reconstructionIssues
)
: displayReconstructableInfo.reconstructionIssues;
} }
imageSet.isModalitySupported = true;
return imageSet; return imageSet;
}; };
@@ -957,38 +1002,36 @@ function _getDisplaySetFromSopClassModule(
} }
/** /**
* Returns the referenced series instance UIDs by searching the information in the * Returns the referenced series instance UIDs by searching the information in the
* ReferencedSeriesSequence. * ReferencedSeriesSequence.
* @param {object} derivatedDisplaySet.metadata * @param {object} derivatedDisplaySet.metadata
* @return {array[string]} referenced series instance UIDs. * @return {array[string]} referenced series instance UIDs.
*/ */
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence ( function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
metadata, metadata
) { ) {
if (!metadata.ReferencedSeriesSequence) { if (!metadata.ReferencedSeriesSequence) {
return; return;
} }
let referencedSeriesInstanceUIDs; let referencedSeriesInstanceUIDs;
const ReferencedSeriesSequence = _toArray( const ReferencedSeriesSequence = _toArray(metadata.ReferencedSeriesSequence);
metadata.ReferencedSeriesSequence
);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map( referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID ReferencedSeries => ReferencedSeries.SeriesInstanceUID
); );
return referencedSeriesInstanceUIDs; return referencedSeriesInstanceUIDs;
}; }
/** /**
* Returns the referenced series instance UIDs by searching the information in the * Returns the referenced series instance UIDs by searching the information in the
* ReferencedImageSequence. * ReferencedImageSequence.
* @param {object} derivatedDisplaySet.metadata * @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets * @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs. * @return {array[string]} referenced series instance UIDs.
*/ */
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence ( function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
metadata, metadata,
displaySets displaySets
) { ) {
@@ -1009,22 +1052,25 @@ function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
ReferencedSOPInstanceUID ReferencedSOPInstanceUID
); );
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) { if (
referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0
) {
break; break;
} }
} }
return referencedSeriesInstanceUIDs; return referencedSeriesInstanceUIDs;
}; }
/** /**
* Returns the referenced series instance UIDs by searching the information in the * Returns the referenced series instance UIDs by searching the information in the
* SourceImageSequence. * SourceImageSequence.
* @param {object} derivatedDisplaySet.metadata * @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets * @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs. * @return {array[string]} referenced series instance UIDs.
*/ */
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence ( function _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
metadata, metadata,
displaySets displaySets
) { ) {
@@ -1034,9 +1080,7 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
SourceImageSequence = metadata.SourceImageSequence; SourceImageSequence = metadata.SourceImageSequence;
} else { } else {
const { PerFrameFunctionalGroupsSequence } = metadata; const { PerFrameFunctionalGroupsSequence } = metadata;
const firstFunctionalGroups = _toArray( const firstFunctionalGroups = _toArray(PerFrameFunctionalGroupsSequence)[0];
PerFrameFunctionalGroupsSequence
)[0];
if (firstFunctionalGroups) { if (firstFunctionalGroups) {
const { DerivationImageSequence } = firstFunctionalGroups; const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence; SourceImageSequence = DerivationImageSequence;
@@ -1056,22 +1100,25 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
displaySets, displaySets,
ReferencedSOPInstanceUID ReferencedSOPInstanceUID
); );
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) { if (
referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0
) {
break; break;
} }
} }
return referencedSeriesInstanceUIDs; return referencedSeriesInstanceUIDs;
}; }
/** /**
* Returns the referenced series instance UIDs by searching the information in the * Returns the referenced series instance UIDs by searching the information in the
* SOPInstanceUID of the displaySets. * SOPInstanceUID of the displaySets.
* @param {array[object]} displaysets * @param {array[object]} displaysets
* @param {string} SOPInstanceUID * @param {string} SOPInstanceUID
* @return {array[string]} referenced series instance UIDs. * @return {array[string]} referenced series instance UIDs.
*/ */
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID ( function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
displaySets, displaySets,
SOPInstanceUID SOPInstanceUID
) { ) {
@@ -1092,10 +1139,10 @@ function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
} }
} }
} }
}; }
function _toArray(arrayOrObject) { function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject]; return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
} }
export {StudyMetadata}; export { StudyMetadata };
+3
View File
@@ -20,6 +20,7 @@ import ui from './ui';
import user from './user.js'; import user from './user.js';
import errorHandler from './errorHandler.js'; import errorHandler from './errorHandler.js';
import utils, { hotkeys } from './utils/'; import utils, { hotkeys } from './utils/';
import str2ab from './utils/str2ab';
import { import {
UINotificationService, UINotificationService,
@@ -96,6 +97,8 @@ export {
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
LoggerService, LoggerService,
str2ab,
}; };
export { OHIF }; export { OHIF };
+1
View File
@@ -26,6 +26,7 @@ describe('Top level exports', () => {
'cornerstone', 'cornerstone',
'default', // 'default', //
'errorHandler', 'errorHandler',
'str2ab',
'string', 'string',
'ui', 'ui',
'user', 'user',
-13
View File
@@ -78,17 +78,6 @@ export const setActiveViewportSpecificData = viewportSpecificData => ({
/** /**
* NOT-VIEWPORT * NOT-VIEWPORT
*/ */
export const setStudyLoadingProgress = (progressId, progressData) => ({
type: 'SET_STUDY_LOADING_PROGRESS',
progressId,
progressData,
});
export const clearStudyLoadingProgress = progressId => ({
type: 'CLEAR_STUDY_LOADING_PROGRESS',
progressId,
});
export const setUserPreferences = state => ({ export const setUserPreferences = state => ({
type: SET_USER_PREFERENCES, type: SET_USER_PREFERENCES,
state, state,
@@ -134,8 +123,6 @@ const actions = {
/** /**
* NOT-VIEWPORT * NOT-VIEWPORT
*/ */
setStudyLoadingProgress,
clearStudyLoadingProgress,
setUserPreferences, setUserPreferences,
setExtensionData, setExtensionData,
setTimepoints, setTimepoints,
-2
View File
@@ -11,8 +11,6 @@ describe('actions', () => {
'setLayout', 'setLayout',
'clearViewportSpecificData', 'clearViewportSpecificData',
'setActiveViewportSpecificData', 'setActiveViewportSpecificData',
'setStudyLoadingProgress',
'clearStudyLoadingProgress',
'setUserPreferences', 'setUserPreferences',
'setExtensionData', 'setExtensionData',
'setTimepoints', 'setTimepoints',
@@ -1,5 +1,4 @@
import extensions from './extensions'; import extensions from './extensions';
import loading from './loading';
import preferences from './preferences'; import preferences from './preferences';
import servers from './servers'; import servers from './servers';
import studies from './studies'; import studies from './studies';
@@ -8,7 +7,6 @@ import viewports from './viewports';
const reducers = { const reducers = {
extensions, extensions,
loading,
preferences, preferences,
servers, servers,
studies, studies,
@@ -1,33 +0,0 @@
import cloneDeep from 'lodash.clonedeep';
const defaultState = {
progress: {},
lastUpdated: null,
};
const loading = (state = defaultState, action) => {
let progress;
let lastUpdated;
switch (action.type) {
case 'SET_STUDY_LOADING_PROGRESS':
progress = cloneDeep(state).progress;
progress[action.progressId] = action.progressData;
// This is a workaround so we can easily identify changes
// to the progress object without doing deep comparison.
lastUpdated = new Date().getTime();
return Object.assign({}, state, { progress, lastUpdated });
case 'CLEAR_STUDY_LOADING_PROGRESS':
progress = cloneDeep(state).progress;
delete progress[action.progressId];
lastUpdated = new Date().getTime();
return Object.assign({}, state, { progress, lastUpdated });
default:
return state;
}
};
export default loading;
@@ -0,0 +1,115 @@
import { api } from 'dicomweb-client';
/**
* An implementation of the static wado client, that fetches data from
* a static response rather than actually doing real queries. This allows
* fast encoding of test data, but because it is static, anything actually
* performing searches doesn't work. This version fixes the query issue
* by manually implementing a query option.
*/
export default class StaticWadoClient extends api.DICOMwebClient {
static filterKeys = {
"StudyInstanceUID": "0020000D",
"PatientName": "00100010",
"00100020": "mrn",
"PatientID": "00100020",
"StudyDescription": "00081030",
"StudyDate": "00080020",
"ModalitiesInStudy": "00080061",
AccessionNumber: "00080050",
};
constructor(qidoConfig) {
super(qidoConfig);
this.staticWado = qidoConfig.staticWado;
}
/**
* Replace the search for studies remote query with a local version which
* retrieves a complete query list and then sub-selects from it locally.
* @param {*} options
* @returns
*/
async searchForStudies(options) {
if (!this.staticWado) return super.searchForStudies(options);
let searchResult = await super.searchForStudies(options);
const { queryParams } = options;
if (!queryParams) return searchResult;
const filtered = searchResult.filter(study => {
for (const key of Object.keys(StaticWadoClient.filterKeys)) {
if (!this.filterItem(key, queryParams, study)) return false;
}
return true;
});
return filtered;
}
/**
* Compares values, matching any instance of desired to any instance of
* actual by recursively go through the paired set of values. That is,
* this is O(m*n) where m is how many items in desired and n is the length of actual
* Then, at the individual item node, compares the Alphabetic name if present,
* and does a sub-string matching on string values, and otherwise does an
* exact match comparison.
*
* @param {*} desired
* @param {*} actual
* @returns true if the values match
*/
compareValues(desired, actual) {
if (Array.isArray(desired)) {
return desired.find(item => this.compareValues(item, actual));
}
if (Array.isArray(actual)) {
return actual.find(actualItem => this.compareValues(desired, actualItem));
}
if (actual && actual.Alphabetic) {
actual = actual.Alphabetic;
}
if (typeof (actual) == 'string') {
if (actual.length === 0) return true;
if (desired.length === 0 || desired === '*') return true;
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`)
return actual.indexOf(desired.substring(1, desired.length - 1)) != -1;
} else if (desired[desired.length - 1] === '*') {
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
} else if (desired[0] === '*') {
return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1;
}
}
return desired === actual;
}
/** Compares a pair of dates to see if the value is within the range */
compareDateRange(range, value) {
if (!value) return true;
const dash = range.indexOf('-');
if (dash === -1) return this.compareValues(range, value);
const start = range.substring(0, dash);
const end = range.substring(dash + 1);
return (!start || value >= start) &&
(!end || value <= end);
}
/**
* Filters the return list by the query parameters.
*
* @param {*} key
* @param {*} queryParams
* @param {*} study
* @returns
*/
filterItem(key, queryParams, study) {
const altKey = StaticWadoClient.filterKeys[key] || key;
if (!queryParams) return true;
const testValue = queryParams[key] || queryParams[altKey];
if (!testValue) return true;
const valueElem = study[key] || study[altKey];
if (!valueElem) return false;
if (valueElem.vr == 'DA') return this.compareDateRange(testValue, valueElem.Value[0]);
const value = valueElem.Value;
return this.compareValues(testValue, value) && true;
}
}
@@ -1,4 +1,5 @@
import { api } from 'dicomweb-client'; import { api } from 'dicomweb-client';
import StaticWadoClient from './StaticWadoClient';
import DICOMWeb from '../../../DICOMWeb/'; import DICOMWeb from '../../../DICOMWeb/';
import errorHandler from '../../../errorHandler'; import errorHandler from '../../../errorHandler';
@@ -115,14 +116,18 @@ function resultDataToStudies(resultData) {
} }
export default function Studies(server, filter) { export default function Studies(server, filter) {
const { staticWado } = server;
const config = { const config = {
...server,
url: server.qidoRoot, url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()], requestHooks: [getXHRRetryRequestHook()],
}; };
const dicomWeb = new api.DICOMwebClient(config); const dicomWeb = staticWado
? new StaticWadoClient(config)
: new api.DICOMwebClient(config);
server.qidoSupportsIncludeField = server.qidoSupportsIncludeField =
server.qidoSupportsIncludeField === undefined server.qidoSupportsIncludeField === undefined
? true ? true
@@ -1,4 +1,4 @@
import { api } from 'dicomweb-client'; import StaticWadoClient from '../qido/StaticWadoClient';
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import DICOMWeb from '../../../DICOMWeb/'; import DICOMWeb from '../../../DICOMWeb/';
import RetrieveMetadataLoader from './retrieveMetadataLoader'; import RetrieveMetadataLoader from './retrieveMetadataLoader';
@@ -73,7 +73,8 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
configLoad() { configLoad() {
const { server } = this; const { server } = this;
const client = new api.DICOMwebClient({ const client = new StaticWadoClient({
...server,
url: server.qidoRoot, url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server), headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(), errorInterceptor: errorHandler.getHTTPErrorHandler(),
@@ -116,6 +116,11 @@ async function makeSOPInstance(server, study, instance) {
}; };
study.seriesMap[SeriesInstanceUID] = series; study.seriesMap[SeriesInstanceUID] = series;
study.series.push(series); study.series.push(series);
} else {
if (series.SeriesDate === undefined)
series.SeriesDate = naturalizedInstance.SeriesDate;
if (series.SeriesTime === undefined)
series.SeriesTime = naturalizedInstance.SeriesTime;
} }
const wadouri = buildInstanceWadoUrl( const wadouri = buildInstanceWadoUrl(
@@ -1,6 +1,5 @@
import { ReconstructionIssues } from './../enums.js'; import { ReconstructionIssues } from './../enums.js';
/** /**
* Checks if a series is reconstructable to a 3D volume. * Checks if a series is reconstructable to a 3D volume.
* *
@@ -78,7 +77,9 @@ function processSingleframe(instances) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS); reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
} else if (SamplesPerPixel !== firstImageSamplesPerPixel) { } else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS); reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
} else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) { } else if (
!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)
) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION); reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
} }
@@ -92,7 +93,10 @@ function processSingleframe(instances) {
reconstructionIssues.push(ReconstructionIssues.DATASET_4D); reconstructionIssues.push(ReconstructionIssues.DATASET_4D);
} }
return { value: reconstructionIssues.length === 0 ? true : false, reconstructionIssues }; return {
value: reconstructionIssues.length === 0 ? true : false,
reconstructionIssues,
};
} }
/** /**
@@ -116,14 +120,12 @@ function isSpacingUniform(instances, datasetIs4D) {
// If spacing is on a uniform grid but we are missing frames, // If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames. // Allow reconstruction, but pass back the number of missing frames.
if (n > 2) { if (n > 2) {
const lastIpp = instances[n - 1].getData().metadata const lastIpp = instances[n - 1].getData().metadata.ImagePositionPatient;
.ImagePositionPatient;
// We can't reconstruct if we are missing ImagePositionPatient values // We can't reconstruct if we are missing ImagePositionPatient values
if (firstImagePositionPatient && lastIpp) { if (firstImagePositionPatient && lastIpp) {
const averageSpacingBetweenFrames = const averageSpacingBetweenFrames =
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) / _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (n - 1);
(n - 1);
let previousImagePositionPatient = firstImagePositionPatient; let previousImagePositionPatient = firstImagePositionPatient;
@@ -136,7 +138,7 @@ function isSpacingUniform(instances, datasetIs4D) {
previousImagePositionPatient previousImagePositionPatient
); );
if (datasetIs4D && spacingBetweenFrames < 1.e-3) { if (datasetIs4D && spacingBetweenFrames < 1e-3) {
// the dataset is 4D, if the distance is zero, means that we are // the dataset is 4D, if the distance is zero, means that we are
// checking the 4th dimension. Do not return, since we want still to // checking the 4th dimension. Do not return, since we want still to
// check the 3rd dimension spacing. // check the 3rd dimension spacing.
@@ -164,10 +166,13 @@ function isSpacingUniform(instances, datasetIs4D) {
} }
} }
return { isUniform: reconstructionIssues.length === 0 ? true : false, missingFrames, reconstructionIssues }; return {
isUniform: reconstructionIssues.length === 0 ? true : false,
missingFrames,
reconstructionIssues,
};
} }
/** /**
* Check if 4D dataset. * Check if 4D dataset.
* *
@@ -181,7 +186,7 @@ function isSpacingUniform(instances, datasetIs4D) {
* *
* @returns {boolean} dataset4D value. * @returns {boolean} dataset4D value.
*/ */
function _isDataset4D(instances) { function _isDataset4D(instances) {
const n = instances.length; const n = instances.length;
for (let ii = 0; ii < n; ++ii) { for (let ii = 0; ii < n; ++ii) {
const instanceMetadataControl = instances[ii].getData().metadata; const instanceMetadataControl = instances[ii].getData().metadata;
@@ -204,7 +209,12 @@ function isSpacingUniform(instances, datasetIs4D) {
continue; continue;
} }
if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) { if (
_isSameArray(
instanceMetadataControl.ImagePositionPatient,
instanceMetadata.ImagePositionPatient
)
) {
return true; return true;
} }
} }
@@ -265,11 +275,11 @@ function _getSpacingIssue(spacing, averageSpacing) {
function _getPerpendicularDistance(a, b) { function _getPerpendicularDistance(a, b) {
return Math.sqrt( return Math.sqrt(
Math.pow(a[0] - b[0], 2) + Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2) + Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2) Math.pow(a[2] - b[2], 2)
); );
} }
const constructableModalities = ['MR', 'CT', 'PT', 'NM']; const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
export {isDisplaySetReconstructable, isSpacingUniform}; export { isDisplaySetReconstructable, isSpacingUniform };
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.5](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.4...@ohif/ui@1.10.5) (2021-11-30)
**Note:** Version bump only for package @ohif/ui
## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.3...@ohif/ui@1.10.4) (2021-10-26)
**Note:** Version bump only for package @ohif/ui
## [1.10.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.2...@ohif/ui@1.10.3) (2021-10-15)
**Note:** Version bump only for package @ohif/ui
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.1...@ohif/ui@1.10.2) (2021-09-24)
### Bug Fixes
* throttle thumbnail progress updates ([#2557](https://github.com/OHIF/Viewers/issues/2557)) ([66c587b](https://github.com/OHIF/Viewers/commit/66c587b139835201331a64bc24a17901e7583649))
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.0...@ohif/ui@1.10.1) (2021-09-13)
**Note:** Version bump only for package @ohif/ui
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.3...@ohif/ui@1.10.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.2...@ohif/ui@1.9.3) (2021-09-03) ## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.2...@ohif/ui@1.9.3) (2021-09-03)
**Note:** Version bump only for package @ohif/ui **Note:** Version bump only for package @ohif/ui
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.9.3", "version": "1.10.5",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -2,7 +2,7 @@
import './ImageThumbnail.styl'; import './ImageThumbnail.styl';
import { utils } from '@ohif/core'; import { utils } from '@ohif/core';
import React, { useState, useEffect, createRef } from 'react'; import React, { useState, useEffect, createRef, useCallback } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -23,6 +23,7 @@ function ImageThumbnail(props) {
imageId, imageId,
stackPercentComplete, stackPercentComplete,
error: propsError, error: propsError,
showProgressBar,
} = props; } = props;
const [isLoading, setLoading] = useState(false); const [isLoading, setLoading] = useState(false);
@@ -39,7 +40,8 @@ function ImageThumbnail(props) {
loadingOrError = <ViewportLoadingIndicator />; loadingOrError = <ViewportLoadingIndicator />;
} }
const showStackLoadingProgressBar = stackPercentComplete !== undefined; const showStackLoadingProgressBar =
showProgressBar && stackPercentComplete !== undefined;
const shouldRenderToCanvas = () => { const shouldRenderToCanvas = () => {
return imageId && !imageSrc; return imageId && !imageSrc;
@@ -71,11 +73,11 @@ function ImageThumbnail(props) {
} }
}; };
const purgeCancelablePromise = () => { const purgeCancelablePromise = useCallback(() => {
if (cancelablePromise) { if (cancelablePromise) {
cancelablePromise.cancel(); cancelablePromise.cancel();
} }
}; });
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -96,7 +98,13 @@ function ImageThumbnail(props) {
setImagePromise(); setImagePromise();
fetchImagePromise(); fetchImagePromise();
} }
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]); }, [
fetchImagePromise,
image.imageId,
imageId,
purgeCancelablePromise,
setImagePromise,
]);
return ( return (
<div className={classNames('ImageThumbnail', { active: active })}> <div className={classNames('ImageThumbnail', { active: active })}>
@@ -104,14 +112,14 @@ function ImageThumbnail(props) {
{shouldRenderToCanvas() ? ( {shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} /> <canvas ref={canvasRef} width={width} height={height} />
) : ( ) : (
<img <img
className="static-image" className="static-image"
src={imageSrc} src={imageSrc}
//width={this.props.width} //width={this.props.width}
height={height} height={height}
alt={''} alt={''}
/> />
)} )}
</div> </div>
{loadingOrError} {loadingOrError}
{showStackLoadingProgressBar && ( {showStackLoadingProgressBar && (
@@ -135,6 +143,7 @@ ImageThumbnail.propTypes = {
width: PropTypes.number, width: PropTypes.number,
height: PropTypes.number, height: PropTypes.number,
stackPercentComplete: PropTypes.number.isRequired, stackPercentComplete: PropTypes.number.isRequired,
showProgressBar: PropTypes.bool,
}; };
ImageThumbnail.defaultProps = { ImageThumbnail.defaultProps = {
@@ -143,6 +152,7 @@ ImageThumbnail.defaultProps = {
stackPercentComplete: 0, stackPercentComplete: 0,
width: 217, width: 217,
height: 123, height: 123,
showProgressBar: true,
}; };
export default ImageThumbnail; export default ImageThumbnail;
@@ -9,6 +9,7 @@ function StudyBrowser(props) {
onThumbnailClick, onThumbnailClick,
onThumbnailDoubleClick, onThumbnailDoubleClick,
supportsDrag, supportsDrag,
showThumbnailProgressBar,
} = props; } = props;
return ( return (
@@ -24,12 +25,12 @@ function StudyBrowser(props) {
altImageText, altImageText,
displaySetInstanceUID, displaySetInstanceUID,
imageId, imageId,
InstanceNumber, derivedDisplaySetsNumber,
numImageFrames, numImageFrames,
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
stackPercentComplete,
hasWarnings, hasWarnings,
hasDerivedDisplaySets,
} = thumb; } = thumb;
return ( return (
@@ -48,19 +49,20 @@ function StudyBrowser(props) {
// Thumb // Thumb
altImageText={altImageText} altImageText={altImageText}
imageId={imageId} imageId={imageId}
InstanceNumber={InstanceNumber} derivedDisplaySetsNumber={derivedDisplaySetsNumber}
displaySetInstanceUID={displaySetInstanceUID} // used by drop displaySetInstanceUID={displaySetInstanceUID} // used by drop
numImageFrames={numImageFrames} numImageFrames={numImageFrames}
SeriesDescription={SeriesDescription} SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber} SeriesNumber={SeriesNumber}
hasWarnings={hasWarnings} hasWarnings={hasWarnings}
stackPercentComplete={stackPercentComplete} hasDerivedDisplaySets={hasDerivedDisplaySets}
// Events // Events
onClick={onThumbnailClick.bind( onClick={onThumbnailClick.bind(
undefined, undefined,
displaySetInstanceUID displaySetInstanceUID
)} )}
onDoubleClick={onThumbnailDoubleClick} onDoubleClick={onThumbnailDoubleClick}
showProgressBar={showThumbnailProgressBar}
/> />
</div> </div>
); );
@@ -83,7 +85,7 @@ StudyBrowser.propTypes = {
altImageText: PropTypes.string, altImageText: PropTypes.string,
displaySetInstanceUID: PropTypes.string.isRequired, displaySetInstanceUID: PropTypes.string.isRequired,
imageId: PropTypes.string, imageId: PropTypes.string,
InstanceNumber: PropTypes.number, derivedDisplaySetsNumber: PropTypes.number,
numImageFrames: PropTypes.number, numImageFrames: PropTypes.number,
SeriesDescription: PropTypes.string, SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.number, SeriesNumber: PropTypes.number,
@@ -95,6 +97,7 @@ StudyBrowser.propTypes = {
supportsDrag: PropTypes.bool, supportsDrag: PropTypes.bool,
onThumbnailClick: PropTypes.func, onThumbnailClick: PropTypes.func,
onThumbnailDoubleClick: PropTypes.func, onThumbnailDoubleClick: PropTypes.func,
showThumbnailProgressBar: PropTypes.bool,
}; };
StudyBrowser.defaultProps = { StudyBrowser.defaultProps = {
@@ -102,6 +105,7 @@ StudyBrowser.defaultProps = {
supportsDrag: true, supportsDrag: true,
onThumbnailClick: noop, onThumbnailClick: noop,
onThumbnailDoubleClick: noop, onThumbnailDoubleClick: noop,
showThumbnailProgressBar: true,
}; };
export { StudyBrowser }; export { StudyBrowser };
@@ -1,34 +1,43 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import throttle from 'lodash.throttle';
import { useDrag } from 'react-dnd'; import { useDrag } from 'react-dnd';
import { classes } from '@ohif/core';
import ImageThumbnail from './ImageThumbnail'; import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from './../../elements/Icon'; import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip'; import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger'; import { OverlayTrigger } from './../overlayTrigger';
import './Thumbnail.styl'; import './Thumbnail.styl';
const StudyLoadingListener = classes.StudyLoadingListener;
function ThumbnailFooter({ function ThumbnailFooter({
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
InstanceNumber,
numImageFrames, numImageFrames,
hasWarnings hasWarnings,
hasDerivedDisplaySets,
}) { }) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]); const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
const [derivedDisplaySetsActive, derivedDisplaySetsActiveSet] = useState([]);
useEffect(() => { useEffect(() => {
let unmounted = false let unmounted = false;
hasWarnings.then(response => { hasWarnings.then(response => {
if (!unmounted) { if (!unmounted) {
inconsistencyWarningsSet(response) inconsistencyWarningsSet(response);
} }
}) });
hasDerivedDisplaySets.then(response => {
if (!unmounted) {
derivedDisplaySetsActiveSet(response);
}
});
return () => { return () => {
unmounted = true unmounted = true;
} };
}, []) }, [hasWarnings, hasDerivedDisplaySets]);
const infoOnly = !SeriesDescription; const infoOnly = !SeriesDescription;
@@ -41,7 +50,7 @@ function ThumbnailFooter({
); );
}; };
const getWarningContent = (inconsistencyWarnings) => { const getWarningContent = inconsistencyWarnings => {
if (Array.isArray(inconsistencyWarnings)) { if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => { const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>; return <li key={index}>{warn}</li>;
@@ -54,8 +63,8 @@ function ThumbnailFooter({
}; };
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => { const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
return( return (
<React.Fragment> <React.Fragment>
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? ( {inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger <OverlayTrigger
key={SeriesNumber} key={SeriesNumber}
@@ -67,7 +76,9 @@ function ThumbnailFooter({
id="tooltip-left" id="tooltip-left"
> >
<div className="warningTitle">Series Inconsistencies</div> <div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div> <div className="warningContent">
{getWarningContent(inconsistencyWarnings)}
</div>
</Tooltip> </Tooltip>
} }
> >
@@ -79,34 +90,67 @@ function ThumbnailFooter({
</OverlayTrigger> </OverlayTrigger>
) : ( ) : (
<React.Fragment></React.Fragment> <React.Fragment></React.Fragment>
)} )}
</React.Fragment> </React.Fragment>
); );
}; };
const getDerivedInfo = derivedDisplaySetsActive => {
return (
<React.Fragment>
{derivedDisplaySetsActive ? (
<div className="derived">
<Icon name="link" />
</div>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
const getSeriesInformation = ( const getSeriesInformation = (
SeriesNumber, SeriesNumber,
InstanceNumber,
numImageFrames, numImageFrames,
inconsistencyWarnings inconsistencyWarnings,
derivedDisplaySetsActive
) => { ) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) { if (!SeriesNumber && !numImageFrames) {
return; return;
} }
const seriesInformation = const seriesInformation = (
<div className="series-information"> <div className="series-information">
{getInfo(SeriesNumber, 'S:')} <React.Fragment>
{getInfo(InstanceNumber, 'I:')} {SeriesNumber !== undefined ? (
{getInfo(numImageFrames, '', 'image-frames')} getInfo(SeriesNumber, 'S:')
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
<React.Fragment>
{numImageFrames !== undefined ? (
getInfo(numImageFrames, '', 'image-frames')
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
{getDerivedInfo(derivedDisplaySetsActive)}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)} {getWarningInfo(SeriesNumber, inconsistencyWarnings)}
</div> </div>
);
return (seriesInformation); return seriesInformation;
}; };
return ( return (
<div className={classNames('series-details', { 'info-only': infoOnly })}> <div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div> <div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)} {getSeriesInformation(
SeriesNumber,
numImageFrames,
inconsistencyWarnings,
derivedDisplaySetsActive
)}
</div> </div>
); );
} }
@@ -119,19 +163,37 @@ function Thumbnail(props) {
displaySetInstanceUID, displaySetInstanceUID,
imageId, imageId,
imageSrc, imageSrc,
InstanceNumber,
numImageFrames,
SeriesDescription,
SeriesNumber,
hasWarnings,
stackPercentComplete,
StudyInstanceUID, StudyInstanceUID,
onClick, onClick,
onDoubleClick, onDoubleClick,
onMouseDown, onMouseDown,
supportsDrag, supportsDrag,
showProgressBar,
} = props; } = props;
const [stackPercentComplete, setStackPercentComplete] = useState(0);
useEffect(() => {
const onProgressChange = throttle(({ detail }) => {
const { progressId, progressData } = detail;
if (`StackProgress:${displaySetInstanceUID}` === progressId) {
const percent = progressData ? progressData.percentComplete : 0;
setStackPercentComplete(percent);
}
}, 100);
document.addEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
return () => {
document.removeEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
};
}, [displaySetInstanceUID]);
const [collectedProps, drag, dragPreview] = useDrag({ const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget` // `droppedItem` in `dropTarget`
// The only data it will have access to // The only data it will have access to
@@ -164,6 +226,7 @@ function Thumbnail(props) {
imageId={imageId} imageId={imageId}
error={error} error={error}
stackPercentComplete={stackPercentComplete} stackPercentComplete={stackPercentComplete}
showProgressBar={showProgressBar}
/> />
)} )}
{/* SHOW TEXT ALTERNATIVE */} {/* SHOW TEXT ALTERNATIVE */}
@@ -197,12 +260,13 @@ Thumbnail.propTypes = {
altImageText: PropTypes.string, altImageText: PropTypes.string,
SeriesDescription: PropTypes.string, SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hasWarnings: PropTypes.instanceOf(Promise), hasWarnings: PropTypes.instanceOf(Promise),
hasDerivedDisplaySets: PropTypes.instanceOf(Promise),
numImageFrames: PropTypes.number, numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func, onDoubleClick: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
showProgressBar: PropTypes.bool,
}; };
Thumbnail.defaultProps = { Thumbnail.defaultProps = {
@@ -6,10 +6,13 @@ import React, {
useEffect, useEffect,
} from 'react'; } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { classes } from '@ohif/core';
import SnackbarContainer from '../components/snackbar/SnackbarContainer'; import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes'; import SnackbarTypes from '../components/snackbar/SnackbarTypes';
const { LogManager } = classes;
const SnackbarContext = createContext(null); const SnackbarContext = createContext(null);
export const useSnackbarContext = () => useContext(SnackbarContext); export const useSnackbarContext = () => useContext(SnackbarContext);
@@ -28,6 +31,20 @@ const SnackbarProvider = ({ children, service }) => {
const [count, setCount] = useState(1); const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]); const [snackbarItems, setSnackbarItems] = useState([]);
useEffect(() => {
const onLogHandler = ({ type, notify, title, message }) => {
if (notify) {
show({ type, title, message });
}
};
LogManager.subscribe(LogManager.EVENTS.OnLog, onLogHandler);
return () => {
LogManager.unsubscribe(LogManager.EVENTS.OnLog, onLogHandler);
};
}, [show]);
const show = useCallback( const show = useCallback(
options => { options => {
if (!options || (!options.title && !options.message)) { if (!options || (!options.title && !options.message)) {
+65 -4
View File
@@ -12,8 +12,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin'); const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin'); const { InjectManifest } = require('workbox-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// ~~ Rules // ~~ Rules
const extractStyleChunksRule = require('./rules/extractStyleChunks.js'); const extractStyleChunksRule = require('./rules/extractStyleChunks.js');
// ~~ Directories // ~~ Directories
@@ -28,6 +26,16 @@ const PROXY_TARGET = process.env.PROXY_TARGET;
const PROXY_DOMAIN = process.env.PROXY_DOMAIN; const PROXY_DOMAIN = process.env.PROXY_DOMAIN;
const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`; const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
const setHeaders = (res, path) => {
res.setHeader('Content-Type', 'text/plain')
if (path.indexOf('.gz') !== -1) {
res.setHeader('Content-Encoding', 'gzip')
} else if (path.indexOf('.br') !== -1) {
res.setHeader('Content-Encoding', 'br')
}
}
module.exports = (env, argv) => { module.exports = (env, argv) => {
const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR }); const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR });
const isProdBuild = process.env.NODE_ENV === 'production'; const isProdBuild = process.env.NODE_ENV === 'production';
@@ -49,6 +57,16 @@ module.exports = (env, argv) => {
} }
}, },
}, },
resolve: {
// We use this alias and the CopyPlugin below to support using the dynamic-import version
// of WADO Image Loader, but only when building a PWA. When we build a package, we must use the
// bundled version of WADO Image Loader so we can produce a single file for the viewer.
// (Note: script-tag version of the viewer will no longer be supported in OHIF v3)
alias: {
'cornerstone-wado-image-loader':
'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js',
},
},
module: { module: {
rules: [...extractStyleChunksRule(isProdBuild)], rules: [...extractStyleChunksRule(isProdBuild)],
}, },
@@ -78,6 +96,11 @@ module.exports = (env, argv) => {
from: `${PUBLIC_DIR}/${APP_CONFIG}`, from: `${PUBLIC_DIR}/${APP_CONFIG}`,
to: `${DIST_DIR}/app-config.js`, to: `${DIST_DIR}/app-config.js`,
}, },
{
from:
'../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
to: DIST_DIR,
},
]), ]),
// https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#webpack-4-standalone-installation // https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#webpack-4-standalone-installation
new ExtractCssChunksPlugin({ new ExtractCssChunksPlugin({
@@ -102,6 +125,15 @@ module.exports = (env, argv) => {
// maximumFileSizeToCacheInBytes: 4 * 1024 * 1024 // maximumFileSizeToCacheInBytes: 4 * 1024 * 1024
}), }),
], ],
optimization: {
splitChunks: {
// include all types of chunks
chunks: 'all',
},
//runtimeChunk: 'single',
minimize: isProdBuild,
sideEffects: true,
},
// https://webpack.js.org/configuration/dev-server/ // https://webpack.js.org/configuration/dev-server/
devServer: { devServer: {
// gzip compression of everything served // gzip compression of everything served
@@ -112,11 +144,40 @@ module.exports = (env, argv) => {
hot: true, hot: true,
open: true, open: true,
port: 3000, port: 3000,
host: '0.0.0.0', client: {
public: 'http://localhost:' + 3000, overlay: { errors: true, warnings: false },
},
'static': [
{
directory: path.join(require('os').homedir(), 'dicomweb'),
staticOptions: {
extensions: ['gz', 'br'],
index: "index.json.gz",
redirect: true,
setHeaders,
},
publicPath: '/dicomweb',
},
{
directory: '../../testdata',
staticOptions: {
extensions: ['gz', 'br'],
index: "index.json.gz",
redirect: true,
setHeaders,
},
publicPath: '/testdata',
},
],
//public: 'http://localhost:' + 3000,
//writeToDisk: true,
historyApiFallback: { historyApiFallback: {
disableDotRule: true, disableDotRule: true,
}, },
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
}, },
}); });
+161
View File
@@ -3,6 +3,167 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.12.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.12...@ohif/viewer@4.12.13) (2022-01-29)
**Note:** Version bump only for package @ohif/viewer
## [4.12.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.11...@ohif/viewer@4.12.12) (2022-01-20)
**Note:** Version bump only for package @ohif/viewer
## [4.12.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.10...@ohif/viewer@4.12.11) (2022-01-19)
**Note:** Version bump only for package @ohif/viewer
## [4.12.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.9...@ohif/viewer@4.12.10) (2022-01-13)
**Note:** Version bump only for package @ohif/viewer
## [4.12.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.8...@ohif/viewer@4.12.9) (2021-12-23)
**Note:** Version bump only for package @ohif/viewer
## [4.12.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.7...@ohif/viewer@4.12.8) (2021-12-23)
**Note:** Version bump only for package @ohif/viewer
## [4.12.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.6...@ohif/viewer@4.12.7) (2021-12-02)
**Note:** Version bump only for package @ohif/viewer
## [4.12.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.5...@ohif/viewer@4.12.6) (2021-11-30)
**Note:** Version bump only for package @ohif/viewer
## [4.12.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.4...@ohif/viewer@4.12.5) (2021-10-27)
**Note:** Version bump only for package @ohif/viewer
## [4.12.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.3...@ohif/viewer@4.12.4) (2021-10-26)
**Note:** Version bump only for package @ohif/viewer
## [4.12.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.2...@ohif/viewer@4.12.3) (2021-10-25)
**Note:** Version bump only for package @ohif/viewer
## [4.12.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.1...@ohif/viewer@4.12.2) (2021-10-15)
**Note:** Version bump only for package @ohif/viewer
## [4.12.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.0...@ohif/viewer@4.12.1) (2021-10-06)
**Note:** Version bump only for package @ohif/viewer
# [4.12.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.2...@ohif/viewer@4.12.0) (2021-10-04)
### Features
* Added support for WADO servers built with static hosting (e.g. S3) ([#2566](https://github.com/OHIF/Viewers/issues/2566)) ([a046c6c](https://github.com/OHIF/Viewers/commit/a046c6c9017a84e5ead71d5113b5e892333b4130))
## [4.11.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.1...@ohif/viewer@4.11.2) (2021-09-28)
**Note:** Version bump only for package @ohif/viewer
## [4.11.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.0...@ohif/viewer@4.11.1) (2021-09-24)
**Note:** Version bump only for package @ohif/viewer
# [4.11.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.10.1...@ohif/viewer@4.11.0) (2021-09-23)
### Features
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
## [4.10.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.10.0...@ohif/viewer@4.10.1) (2021-09-13)
**Note:** Version bump only for package @ohif/viewer
# [4.10.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.24...@ohif/viewer@4.10.0) (2021-09-09)
### Features
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
## [4.9.24](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.23...@ohif/viewer@4.9.24) (2021-09-03) ## [4.9.24](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.23...@ohif/viewer@4.9.24) (2021-09-03)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
@@ -333,6 +333,7 @@ describe('OHIF User Preferences', () => {
.scrollIntoView() .scrollIntoView()
.click(); .click();
// Header should be in "English (USA)"" // Header should be in "English (USA)""
cy.get('.research-use') cy.get('.research-use')
.scrollIntoView() .scrollIntoView()
@@ -560,6 +561,13 @@ describe('OHIF User Preferences', () => {
.scrollIntoView() .scrollIntoView()
.click(); .click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
});
// Open User Preferences modal again // Open User Preferences modal again
cy.openPreferences(); cy.openPreferences();
@@ -668,6 +676,13 @@ describe('OHIF User Preferences', () => {
// Save changes // Save changes
cy.get('@saveBtn').click(); cy.get('@saveBtn').click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
});
// Open User Preferences modal // Open User Preferences modal
cy.openPreferences(); cy.openPreferences();
// Navigate to Window Level tab // Navigate to Window Level tab
@@ -85,7 +85,7 @@ describe('OHIF Save Measurements', function() {
// cy.get('.sb-closeIcon').click(); // cy.get('.sb-closeIcon').click();
// }); // });
it('checks if warning message is displayed on measurements of unsupported tools', function() { /*it('checks if warning message is displayed on measurements of unsupported tools', function() {
// Add measurement for unsupported tool in the viewport // Add measurement for unsupported tool in the viewport
cy.addAngleMeasurement(); cy.addAngleMeasurement();
@@ -108,7 +108,7 @@ describe('OHIF Save Measurements', function() {
// Close Measurements panel // Close Measurements panel
cy.get('@measurementsBtn').click(); cy.get('@measurementsBtn').click();
}); });*/
/*it('checks if measurements of unsupported tools were not saved', function() { /*it('checks if measurements of unsupported tools were not saved', function() {
// Add measurement for supported tool in the viewport // Add measurement for supported tool in the viewport
+23 -13
View File
@@ -1,4 +1,5 @@
import '@percy/cypress'; import '@percy/cypress';
import { enable } from 'cornerstone-core';
import 'cypress-file-upload'; import 'cypress-file-upload';
import { DragSimulator } from '../helpers/DragSimulator.js'; import { DragSimulator } from '../helpers/DragSimulator.js';
import { import {
@@ -199,6 +200,11 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
.its('cornerstone') .its('cornerstone')
.then({ timeout }, $cornerstone => { .then({ timeout }, $cornerstone => {
return new Cypress.Promise(resolve => { return new Cypress.Promise(resolve => {
const onEnabled = enabledEvt => {
const element = enabledEvt.detail.element;
element.addEventListener('cornerstoneimagerendered', onEvent);
};
const onEvent = renderedEvt => { const onEvent = renderedEvt => {
const element = renderedEvt.detail.element; const element = renderedEvt.detail.element;
@@ -209,15 +215,17 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
); );
resolve(); resolve();
}; };
const onEnabled = enabledEvt => { const enabledElements = $cornerstone.getEnabledElements();
const element = enabledEvt.detail.element; if (enabledElements && enabledElements.length && !enabledElements[0].invalid) {
// Sometimes the page finishes rendering before this gets run,
element.addEventListener('cornerstoneimagerendered', onEvent); // if so, just resolve immediately.
}; resolve();
$cornerstone.events.addEventListener( } else {
'cornerstoneelementenabled', $cornerstone.events.addEventListener(
onEnabled 'cornerstoneelementenabled',
); onEnabled
);
}
}); });
}); });
} }
@@ -504,6 +512,9 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => {
cy.get('@restoreBtn').click(); cy.get('@restoreBtn').click();
}); });
// Click on Save Button
cy.get('@saveBtn').click();
// Close Success Message overlay (if displayed) // Close Success Message overlay (if displayed)
cy.get('body').then(body => { cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) { if (body.find('.sb-closeIcon').length > 0) {
@@ -511,8 +522,6 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => {
.first() .first()
.click({ force: true }); .click({ force: true });
} }
// Click on Save Button
cy.get('@saveBtn').click();
}); });
}); });
@@ -525,6 +534,9 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => {
cy.get('@restoreBtn').click(); cy.get('@restoreBtn').click();
}); });
// Click on Save Button
cy.get('@saveBtn').click();
// Close Success Message overlay (if displayed) // Close Success Message overlay (if displayed)
cy.get('body').then(body => { cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) { if (body.find('.sb-closeIcon').length > 0) {
@@ -532,8 +544,6 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => {
.first() .first()
.click({ force: true }); .click({ force: true });
} }
// Click on Save Button
cy.get('@saveBtn').click();
}); });
}); });
+20 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.9.24", "version": "4.12.13",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -19,21 +19,19 @@
"scripts": { "scripts": {
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress", "build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress", "build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
"build:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js", "build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js", "build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
"build:viewer:package": "yarn run build:package", "build:viewer:package": "yarn run build:package",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch", "dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js",
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch", "dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js",
"dev:viewer": "yarn run dev", "dev:viewer": "yarn run dev",
"dev:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
"start": "yarn run dev", "start": "yarn run dev",
"test:e2e": "cypress open", "test:e2e": "cypress open",
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'", "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: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: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:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
"test:e2e:serve": "serve -n -l 3000 -s dist", "test:e2e:serve": "cross-env APP_CONFIG=config/e2e.js yarn start",
"test:unit": "jest --watchAll", "test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage", "test:unit:ci": "jest --ci --runInBand --collectCoverage",
"prepublishOnly": "yarn run build:package", "prepublishOnly": "yarn run build:package",
@@ -49,28 +47,28 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.13.15", "@ohif/core": "^2.16.8",
"@ohif/extension-cornerstone": "^2.10.10", "@ohif/extension-cornerstone": "^2.12.4",
"@ohif/extension-debugging": "^0.2.2", "@ohif/extension-debugging": "^0.3.1",
"@ohif/extension-dicom-html": "^1.3.10", "@ohif/extension-dicom-html": "^1.3.15",
"@ohif/extension-dicom-microscopy": "^0.52.0", "@ohif/extension-dicom-microscopy": "^0.52.0",
"@ohif/extension-dicom-pdf": "^1.0.6", "@ohif/extension-dicom-pdf": "^1.1.2",
"@ohif/extension-dicom-rt": "^0.6.14", "@ohif/extension-dicom-rt": "^0.7.6",
"@ohif/extension-dicom-segmentation": "^0.6.11", "@ohif/extension-dicom-segmentation": "^0.7.6",
"@ohif/extension-lesion-tracker": "^0.2.1", "@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.11.16", "@ohif/extension-vtk": "^1.12.12",
"@ohif/i18n": "^0.53.0", "@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.9.3", "@ohif/ui": "^1.10.5",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.6.1",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.9",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^6.0.6",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^4.1.0",
"dcmjs": "0.18.8", "dcmjs": "0.19.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.11",
"dicomweb-client": "^0.8.1", "dicomweb-client": "^0.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
+10
View File
@@ -5,6 +5,16 @@ window.config = {
showStudyList: true, showStudyList: true,
filterQueryParam: false, filterQueryParam: false,
disableServersCache: false, disableServersCache: false,
studyPrefetcher: {
enabled: true,
order: 'closest',
displaySetCount: 3,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
maxNumPrefetchRequests: 100,
displayProgress: true,
includeActiveDisplaySet: true,
},
servers: { servers: {
dicomWeb: [ dicomWeb: [
{ {
+116
View File
@@ -0,0 +1,116 @@
window.config = {
routerBasename: '/',
showStudyList: true,
servers: {
dicomWeb: [
{
name: 'LocalStatic',
wadoUriRoot: '/testdata',
qidoRoot: '/testdata',
wadoRoot: '/testdata',
qidoSupportsIncludeField: false,
imageRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
staticWado: 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'],
},
],
};
@@ -0,0 +1,20 @@
window.config = {
routerBasename: '/',
showStudyList: true,
servers: {
dicomWeb: [
{
name: 'LocalStatic',
wadoUriRoot: '/dicomweb',
qidoRoot: '/dicomweb',
wadoRoot: '/dicomweb',
qidoSupportsIncludeField: false,
imageRendering: 'wadors',
thumbnailRendering: 'thumbnail',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
staticWado: true,
},
],
},
};
+9
View File
@@ -1,6 +1,15 @@
window.config = { window.config = {
routerBasename: '/pwa', routerBasename: '/pwa',
showStudyList: true, showStudyList: true,
studyPrefetcher: {
enabled: true,
order: 'closest',
displaySetCount: 3,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
displayProgress: true,
includeActiveDisplaySet: true,
},
servers: { servers: {
dicomWeb: [ dicomWeb: [
{ {
@@ -1,42 +1,23 @@
import { Component } from "react"; import { useEffect } from 'react';
import PropTypes from "prop-types"; import OHIF from '@ohif/core';
import OHIF from "@ohif/core";
class StudyLoadingMonitor extends Component { const { StudyLoadingListener } = OHIF.classes;
static propTypes = {
studies: PropTypes.array.isRequired,
setStudyLoadingProgress: PropTypes.func.isRequired,
clearStudyLoadingProgress: PropTypes.func.isRequired
};
componentDidMount() { const StudyLoadingMonitor = ({ studies }) => {
// TODO: This is pretty ugly. The thing is that the StudyLoadingListener useEffect(() => {
// needs to update the Redux store, but shouldn't know that it exists. const studyLoadingListener = StudyLoadingListener.getInstance();
// I am therefore passing in some functions to update the store instead,
// but this should definitely be cleaned up somehow. if (studies && studies.length > 0) {
const options = { studyLoadingListener.clear();
_setProgressData: (progressId, progressData) => { studyLoadingListener.addStudies(studies);
this.props.setStudyLoadingProgress(progressId, progressData); }
},
_clearProgressById: progressId => { return () => {
this.props.clearStudyLoadingProgress(progressId); studyLoadingListener.clear();
}
}; };
}, [studies]);
const { StudyLoadingListener } = OHIF.classes; return null;
this.studyLoadingListener = StudyLoadingListener.getInstance(options); };
this.studyLoadingListener.clear();
this.studyLoadingListener.addStudies(this.props.studies);
}
render() {
return null;
}
componentWillUnmount() {
// Destroy stack loading listeners when we close the viewer
this.studyLoadingListener.clear();
}
}
export default StudyLoadingMonitor; export default StudyLoadingMonitor;
@@ -0,0 +1,15 @@
.StudyPrefetcher {
height: 5px;
width: 100%;
display: flex;
flex-direction: row;
background-color: var(--default-color);
}
.StudyPrefetcher .item {
height: 5px;
}
.StudyPrefetcher .item.cached {
background-color: black;
}
@@ -1,29 +1,93 @@
import { Component } from "react"; import React, { useEffect } from 'react';
import PropTypes from "prop-types"; import { classes, utils } from '@ohif/core';
import OHIF from "@ohif/core"; import PropTypes from 'prop-types';
import cs from 'cornerstone-core';
const { StudyPrefetcher } = OHIF.classes; import './StudyPrefetcher.css';
class StudyPrefetcherComponent extends Component { const StudyPrefetcher = ({ studies, options }) => {
static propTypes = { useEffect(() => {
studies: PropTypes.array const studyPrefetcher = classes.StudyPrefetcher.getInstance(
}; studies,
options
);
const studiesMetadata = studies.map(s =>
utils.studyMetadataManager.get(s.StudyInstanceUID)
);
studyPrefetcher.setStudies(studiesMetadata);
componentDidMount() { const onNewImage = ({ detail }) => {
const { studies } = this.props; /**
* When images are cached the viewport will load instantly and
* the display sets will not be available at this point in time.
*
* This code add display sets and updates the study prefetcher metadata.
*/
const studiesMetadata = studies.map(s => {
const studyMetadata = utils.studyMetadataManager.get(
s.StudyInstanceUID
);
const displaySets = studyMetadata.getDisplaySets();
if (!displaySets || displaySets.length < 1) {
s.displaySets.forEach(ds => studyMetadata.addDisplaySet(ds));
}
return studyMetadata;
});
studyPrefetcher.setStudies(studiesMetadata);
this.studyPrefetcher = StudyPrefetcher.getInstance(); const study = studyPrefetcher.getStudy(detail.image);
this.studyPrefetcher.setStudies(studies); const series = studyPrefetcher.getSeries(study, detail.image);
} const instance = studyPrefetcher.getInstance(series, detail.image);
render() { if (study.displaySets && study.displaySets.length > 0) {
return null; const {
} displaySetInstanceUID,
} = studyPrefetcher.getDisplaySetBySOPInstanceUID(
study.displaySets,
instance
);
studyPrefetcher.prefetch(detail.element, displaySetInstanceUID);
}
};
componentWillUnmount() { const onElementEnabled = ({ detail }) => {
// Stop prefetching when we close the viewer detail.element.addEventListener(cs.EVENTS.NEW_IMAGE, onNewImage);
this.studyPrefetcher.destroy(); };
}
}
export default StudyPrefetcherComponent; cs.events.addEventListener(cs.EVENTS.ELEMENT_ENABLED, onElementEnabled);
return () => {
cs.events.removeEventListener(
cs.EVENTS.ELEMENT_ENABLED,
onElementEnabled
);
studyPrefetcher.destroy();
};
}, [options, studies]);
return null;
};
StudyPrefetcher.propTypes = {
studies: PropTypes.array.isRequired,
options: PropTypes.shape({
enabled: PropTypes.bool,
order: PropTypes.string,
displaySetCount: PropTypes.number,
preventCache: PropTypes.bool,
prefetchDisplaySetsTimeout: PropTypes.number,
includeActiveDisplaySet: PropTypes.bool,
}),
};
StudyPrefetcher.defaultProps = {
options: {
order: 'closest',
displaySetCount: 1,
preventCache: false,
prefetchDisplaySetsTimeout: 300,
includeActiveDisplaySet: false,
},
};
export default StudyPrefetcher;
@@ -1,44 +1,11 @@
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { StudyBrowser } from '@ohif/ui';
import cloneDeep from 'lodash.clonedeep';
import findDisplaySetByUID from './findDisplaySetByUID'; import findDisplaySetByUID from './findDisplaySetByUID';
import { servicesManager } from './../App.js'; import { servicesManager } from './../App.js';
import { StudyBrowser } from '../../../ui/src/components/studyBrowser/StudyBrowser';
const { studyMetadataManager } = OHIF.utils;
const { setActiveViewportSpecificData } = OHIF.redux.actions; const { setActiveViewportSpecificData } = OHIF.redux.actions;
// TODO
// - Determine in which display set is active from Redux (activeViewportIndex and layout viewportData)
// - Pass in errors and stack loading progress from Redux
const mapStateToProps = (state, ownProps) => {
// If we know that the stack loading progress details have changed,
// we can try to update the component state so that the thumbnail
// progress bar is updated
const stackLoadingProgressMap = state.loading.progress;
const studiesWithLoadingData = cloneDeep(ownProps.studies);
studiesWithLoadingData.forEach(study => {
study.thumbnails.forEach(data => {
const { displaySetInstanceUID } = data;
const stackId = `StackProgress:${displaySetInstanceUID}`;
const stackProgressData = stackLoadingProgressMap[stackId];
let stackPercentComplete = 0;
if (stackProgressData) {
stackPercentComplete = stackProgressData.percentComplete;
}
data.stackPercentComplete = stackPercentComplete;
});
});
return {
studies: studiesWithLoadingData,
};
};
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch, ownProps) => {
return { return {
onThumbnailClick: displaySetInstanceUID => { onThumbnailClick: displaySetInstanceUID => {
@@ -47,10 +14,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
displaySetInstanceUID displaySetInstanceUID
); );
const { LoggerService, UINotificationService } = servicesManager.services;
if (displaySet.isDerived) { if (displaySet.isDerived) {
const { Modality } = displaySet; const { Modality } = displaySet;
if (Modality === 'SEG' && servicesManager) { if (Modality === 'SEG' && servicesManager) {
const {LoggerService, UINotificationService} = servicesManager.services;
const onDisplaySetLoadFailureHandler = error => { const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message }); LoggerService.error({ error, message: error.message });
UINotificationService.show({ UINotificationService.show({
@@ -61,33 +29,66 @@ const mapDispatchToProps = (dispatch, ownProps) => {
}); });
}; };
const {referencedDisplaySet, activatedLabelmapPromise} = displaySet.getSourceDisplaySet( const {
referencedDisplaySet,
activatedLabelmapPromise,
} = displaySet.getSourceDisplaySet(
ownProps.studyMetadata, ownProps.studyMetadata,
true, true,
onDisplaySetLoadFailureHandler onDisplaySetLoadFailureHandler
); );
displaySet = referencedDisplaySet; displaySet = referencedDisplaySet;
activatedLabelmapPromise.then((activatedLabelmapIndex) => { activatedLabelmapPromise.then(activatedLabelmapIndex => {
const selectionFired = new CustomEvent("extensiondicomsegmentationsegselected", { const selectionFired = new CustomEvent(
"detail": {"activatedLabelmapIndex":activatedLabelmapIndex} 'extensiondicomsegmentationsegselected',
}); {
detail: { activatedLabelmapIndex: activatedLabelmapIndex },
}
);
document.dispatchEvent(selectionFired); document.dispatchEvent(selectionFired);
}); });
} else { } else {
displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata); displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata);
} }
if (!displaySet) { if (!displaySet) {
throw new Error( const error = new Error(
`Referenced series for ${Modality} dataset not present.` `Referenced series for ${Modality} dataset not present.`
); );
const message = `Referenced series for ${Modality} dataset not present.`;
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
} }
}
if (!displaySet) { if (!displaySet) {
throw new Error('Source data not present'); const error = new Error('Source data not present');
} const message = 'Source data not present';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
if (displaySet.isModalitySupported === false) {
const error = new Error('Modality not supported');
const message = 'Modality not supported';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
} }
dispatch(setActiveViewportSpecificData(displaySet)); dispatch(setActiveViewportSpecificData(displaySet));
@@ -96,7 +97,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
}; };
const ConnectedStudyBrowser = connect( const ConnectedStudyBrowser = connect(
mapStateToProps, null,
mapDispatchToProps mapDispatchToProps
)(StudyBrowser); )(StudyBrowser);
@@ -1,26 +0,0 @@
import { connect } from "react-redux";
import StudyLoadingMonitor from "../components/StudyLoadingMonitor.js";
import OHIF from "@ohif/core";
const {
setStudyLoadingProgress,
clearStudyLoadingProgress
} = OHIF.redux.actions;
const mapDispatchToProps = dispatch => {
return {
setStudyLoadingProgress: (progressId, progressData) => {
dispatch(setStudyLoadingProgress(progressId, progressData));
},
clearStudyLoadingProgress: progressId => {
dispatch(clearStudyLoadingProgress(progressId));
}
};
};
const ConnectedStudyLoadingMonitor = connect(
null,
mapDispatchToProps
)(StudyLoadingMonitor);
export default ConnectedStudyLoadingMonitor;
+201 -85
View File
@@ -1,11 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useLogger } from '@ohif/ui';
import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core'; import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core';
import { withDialog } from '@ohif/ui'; import { withDialog } from '@ohif/ui';
import moment from 'moment'; import moment from 'moment';
import ConnectedHeader from './ConnectedHeader.js'; import ConnectedHeader from './ConnectedHeader.js';
import ToolbarRow from './ToolbarRow.js'; import ToolbarRow from './ToolbarRow.js';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js'; import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
@@ -14,7 +13,6 @@ import SidePanel from './../components/SidePanel.js';
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog'; import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
import { extensionManager } from './../App.js'; import { extensionManager } from './../App.js';
import { ReconstructionIssues } from './../../../core/src/enums.js'; import { ReconstructionIssues } from './../../../core/src/enums.js';
import dcmjs from 'dcmjs';
// Contexts // Contexts
import WhiteLabelingContext from '../context/WhiteLabelingContext.js'; import WhiteLabelingContext from '../context/WhiteLabelingContext.js';
@@ -22,8 +20,10 @@ import UserManagerContext from '../context/UserManagerContext';
import AppContext from '../context/AppContext'; import AppContext from '../context/AppContext';
import './Viewer.css'; import './Viewer.css';
import { finished } from 'stream'; import StudyPrefetcher from '../components/StudyPrefetcher.js';
import { cornerstoneWADOImageLoader } from 'cornerstone-wado-image-loader'; import StudyLoadingMonitor from '../components/StudyLoadingMonitor';
const { studyMetadataManager } = OHIF.utils;
class Viewer extends Component { class Viewer extends Component {
static propTypes = { static propTypes = {
@@ -197,11 +197,17 @@ class Viewer extends Component {
]); ]);
} }
const activeViewport = this.props.viewports[this.props.activeViewportIndex]; const activeViewport = this.props.viewports[
const activeDisplaySetInstanceUID = this.props.activeViewportIndex
activeViewport ? activeViewport.displaySetInstanceUID : undefined; ];
const activeDisplaySetInstanceUID = activeViewport
? activeViewport.displaySetInstanceUID
: undefined;
this.setState({ this.setState({
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID), thumbnails: _mapStudiesToThumbnails(
studies,
activeDisplaySetInstanceUID
),
}); });
} }
} }
@@ -211,24 +217,31 @@ class Viewer extends Component {
studies, studies,
isStudyLoaded, isStudyLoaded,
activeViewportIndex, activeViewportIndex,
viewports viewports,
} = this.props; } = this.props;
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports[activeViewportIndex];
const activeDisplaySetInstanceUID = const activeDisplaySetInstanceUID = activeViewport
activeViewport ? activeViewport.displaySetInstanceUID : undefined; ? activeViewport.displaySetInstanceUID
: undefined;
const prevActiveViewport = prevProps.viewports[prevProps.activeViewportIndex]; const prevActiveViewport =
const prevActiveDisplaySetInstanceUID = prevProps.viewports[prevProps.activeViewportIndex];
prevActiveViewport ? prevActiveViewport.displaySetInstanceUID : undefined; const prevActiveDisplaySetInstanceUID = prevActiveViewport
? prevActiveViewport.displaySetInstanceUID
: undefined;
if (studies !== prevProps.studies || if (
studies !== prevProps.studies ||
activeViewportIndex !== prevProps.activeViewportIndex || activeViewportIndex !== prevProps.activeViewportIndex ||
activeDisplaySetInstanceUID !== prevActiveDisplaySetInstanceUID activeDisplaySetInstanceUID !== prevActiveDisplaySetInstanceUID
) { ) {
this.setState({ this.setState({
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID), thumbnails: _mapStudiesToThumbnails(
studies,
activeDisplaySetInstanceUID
),
activeDisplaySetInstanceUID,
}); });
} }
if (isStudyLoaded && isStudyLoaded !== prevProps.isStudyLoaded) { if (isStudyLoaded && isStudyLoaded !== prevProps.isStudyLoaded) {
@@ -288,14 +301,12 @@ class Viewer extends Component {
</UserManagerContext.Consumer> </UserManagerContext.Consumer>
)} )}
</WhiteLabelingContext.Consumer> </WhiteLabelingContext.Consumer>
{/* TOOLBAR */} {/* TOOLBAR */}
<ErrorBoundaryDialog context="ToolbarRow"> <ErrorBoundaryDialog context="ToolbarRow">
<ToolbarRow <ToolbarRow
activeViewport={ activeViewport={
this.props.viewports[this.props.activeViewportIndex] this.props.viewports[this.props.activeViewportIndex]
} }
isDerivedDisplaySetsLoaded={this.props.isDerivedDisplaySetsLoaded}
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen} isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
isRightSidePanelOpen={this.state.isRightSidePanelOpen} isRightSidePanelOpen={this.state.isRightSidePanelOpen}
selectedLeftSidePanel={ selectedLeftSidePanel={
@@ -332,10 +343,9 @@ class Viewer extends Component {
studies={this.props.studies} studies={this.props.studies}
/> />
</ErrorBoundaryDialog> </ErrorBoundaryDialog>
<AppContext.Consumer>
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/} {appContext => <StudyLoadingMonitor studies={this.props.studies} />}
{/*<StudyPrefetcher studies={this.props.studies} />*/} </AppContext.Consumer>
{/* VIEWPORTS + SIDEPANELS */} {/* VIEWPORTS + SIDEPANELS */}
<div className="FlexboxLayout"> <div className="FlexboxLayout">
{/* LEFT */} {/* LEFT */}
@@ -348,10 +358,24 @@ class Viewer extends Component {
activeIndex={this.props.activeViewportIndex} activeIndex={this.props.activeViewportIndex}
/> />
) : ( ) : (
<ConnectedStudyBrowser <AppContext.Consumer>
studies={this.state.thumbnails} {appContext => {
studyMetadata={this.props.studies} const { appConfig } = appContext;
/> const { studyPrefetcher } = appConfig;
const { thumbnails } = this.state;
return (
<ConnectedStudyBrowser
studies={thumbnails}
studyMetadata={this.props.studies}
showThumbnailProgressBar={
studyPrefetcher &&
studyPrefetcher.enabled &&
studyPrefetcher.displayProgress
}
/>
);
}}
</AppContext.Consumer>
)} )}
</SidePanel> </SidePanel>
</ErrorBoundaryDialog> </ErrorBoundaryDialog>
@@ -359,6 +383,22 @@ class Viewer extends Component {
{/* MAIN */} {/* MAIN */}
<div className={classNames('main-content')}> <div className={classNames('main-content')}>
<ErrorBoundaryDialog context="ViewerMain"> <ErrorBoundaryDialog context="ViewerMain">
<AppContext.Consumer>
{appContext => {
const { appConfig } = appContext;
const { studyPrefetcher } = appConfig;
const { studies } = this.props;
return (
studyPrefetcher &&
studyPrefetcher.enabled && (
<StudyPrefetcher
studies={studies}
options={studyPrefetcher}
/>
)
);
}}
</AppContext.Consumer>
<ConnectedViewerMain <ConnectedViewerMain
studies={this.props.studies} studies={this.props.studies}
isStudyLoaded={this.props.isStudyLoaded} isStudyLoaded={this.props.isStudyLoaded}
@@ -391,6 +431,31 @@ class Viewer extends Component {
export default withDialog(Viewer); export default withDialog(Viewer);
/**
* Async function to check if the displaySet has any derived one
*
* @param {*object} displaySet
* @param {*object} study
* @returns {bool}
*/
const _checkForDerivedDisplaySets = async function(displaySet, study) {
let derivedDisplaySetsNumber = 0;
if (
displaySet.Modality &&
!['SEG', 'SR', 'RTSTRUCT', 'RTDOSE'].includes(displaySet.Modality)
) {
const studyMetadata = studyMetadataManager.get(study.StudyInstanceUID);
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: displaySet.SeriesInstanceUID,
});
derivedDisplaySetsNumber = derivedDisplaySets.length;
}
return derivedDisplaySetsNumber > 0;
};
/** /**
* Async function to check if there are any inconsistences in the series. * Async function to check if there are any inconsistences in the series.
* *
@@ -410,31 +475,46 @@ export default withDialog(Viewer);
* @param {*object} displaySet * @param {*object} displaySet
* @returns {[string]} an array of strings containing the warnings * @returns {[string]} an array of strings containing the warnings
*/ */
const _checkForSeriesInconsistencesWarnings = async function (displaySet, studies) { const _checkForSeriesInconsistencesWarnings = async function(
displaySet,
studies
) {
if (displaySet.inconsistencyWarnings) { if (displaySet.inconsistencyWarnings) {
// warnings already checked and cached in displaySet // warnings already checked and cached in displaySet
return displaySet.inconsistencyWarnings; return displaySet.inconsistencyWarnings;
} }
const inconsistencyWarnings = []; const inconsistencyWarnings = [];
if (displaySet.Modality !== 'SEG') { if (displaySet.Modality !== 'SEG') {
if (displaySet.reconstructionIssues && displaySet.reconstructionIssues.length !== 0) { if (
displaySet.reconstructionIssues &&
displaySet.reconstructionIssues.length !== 0
) {
displaySet.reconstructionIssues.forEach(warning => { displaySet.reconstructionIssues.forEach(warning => {
switch (warning) { switch (warning) {
case ReconstructionIssues.DATASET_4D: case ReconstructionIssues.DATASET_4D:
inconsistencyWarnings.push('The dataset is 4D.'); inconsistencyWarnings.push('The dataset is 4D.');
break; break;
case ReconstructionIssues.VARYING_IMAGESDIMENSIONS: case ReconstructionIssues.VARYING_IMAGESDIMENSIONS:
inconsistencyWarnings.push('The dataset frames have different dimensions (rows, columns).'); inconsistencyWarnings.push(
'The dataset frames have different dimensions (rows, columns).'
);
break; break;
case ReconstructionIssues.VARYING_IMAGESCOMPONENTS: case ReconstructionIssues.VARYING_IMAGESCOMPONENTS:
inconsistencyWarnings.push('The dataset frames have different components (Sample per pixel).'); inconsistencyWarnings.push(
'The dataset frames have different components (Sample per pixel).'
);
break; break;
case ReconstructionIssues.VARYING_IMAGESORIENTATION: case ReconstructionIssues.VARYING_IMAGESORIENTATION:
inconsistencyWarnings.push('The dataset frames have different orientation.'); inconsistencyWarnings.push(
'The dataset frames have different orientation.'
);
break; break;
case ReconstructionIssues.IRREGULAR_SPACING: case ReconstructionIssues.IRREGULAR_SPACING:
inconsistencyWarnings.push('The dataset frames have different pixel spacing.'); inconsistencyWarnings.push(
'The dataset frames have different pixel spacing.'
);
break; break;
case ReconstructionIssues.MULTIFFRAMES: case ReconstructionIssues.MULTIFFRAMES:
inconsistencyWarnings.push('The dataset is a multiframes.'); inconsistencyWarnings.push('The dataset is a multiframes.');
@@ -443,39 +523,56 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
break; break;
} }
}); });
inconsistencyWarnings.push('The datasets is not a reconstructable 3D volume. MPR mode is not available.'); inconsistencyWarnings.push(
'The datasets is not a reconstructable 3D volume. MPR mode is not available.'
);
} }
if (displaySet.missingFrames && if (
displaySet.missingFrames &&
(!displaySet.reconstructionIssues || (!displaySet.reconstructionIssues ||
(displaySet.reconstructionIssues && !displaySet.reconstructionIssues.find(warn => warn === ReconstructionIssues.DATASET_4D)))) { (displaySet.reconstructionIssues &&
inconsistencyWarnings.push('The datasets is missing frames: ' + displaySet.missingFrames + '.'); !displaySet.reconstructionIssues.find(
warn => warn === ReconstructionIssues.DATASET_4D
)))
) {
inconsistencyWarnings.push(
'The datasets is missing frames: ' + displaySet.missingFrames + '.'
);
}
if (displaySet.isModalitySupported === false) {
inconsistencyWarnings.push('The datasets modality is not supported.');
} }
} else { } else {
const segMetadata = displaySet.metadata; const segMetadata = displaySet.metadata;
if (!segMetadata) { if (!segMetadata) {
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings; return inconsistencyWarnings;
} }
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false); const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
studies,
false
);
if (!referencedDisplaySet) { if (!referencedDisplaySet) {
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings; return inconsistencyWarnings;
} }
const imageIds = referencedDisplaySet.images.map(image => image.getImageId()); const imageIds = referencedDisplaySet.images.map(image =>
image.getImageId()
);
if (!imageIds || imageIds.length === 0) { if (!imageIds || imageIds.length === 0) {
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings; return inconsistencyWarnings;
} }
for ( for (
let i = 0, groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length; let i = 0,
groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length;
i < groupsLen; i < groupsLen;
++i ++i
) { ) {
const PerFrameFunctionalGroups = segMetadata.PerFrameFunctionalGroupsSequence[i]; const PerFrameFunctionalGroups =
segMetadata.PerFrameFunctionalGroupsSequence[i];
if (!PerFrameFunctionalGroups) { if (!PerFrameFunctionalGroups) {
continue; continue;
} }
@@ -485,29 +582,27 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
SourceImageSequence = segMetadata.SourceImageSequence[i]; SourceImageSequence = segMetadata.SourceImageSequence[i];
} else if (PerFrameFunctionalGroups.DerivationImageSequence) { } else if (PerFrameFunctionalGroups.DerivationImageSequence) {
SourceImageSequence = SourceImageSequence =
PerFrameFunctionalGroups.DerivationImageSequence PerFrameFunctionalGroups.DerivationImageSequence.SourceImageSequence;
.SourceImageSequence;
} }
if (!SourceImageSequence) { if (!SourceImageSequence) {
if (inconsistencyWarnings.length === 0) { if (inconsistencyWarnings.length === 0) {
const warningMessage = 'The segmentation ' + const warningMessage =
'The segmentation ' +
'has frames out of plane respect to the source images.'; 'has frames out of plane respect to the source images.';
inconsistencyWarnings.push(warningMessage); inconsistencyWarnings.push(warningMessage);
} }
continue; continue;
} }
const { const { ReferencedSOPInstanceUID } = SourceImageSequence;
ReferencedSOPInstanceUID,
} = SourceImageSequence;
const imageId = imageIds.find(imageId => { const imageId = imageIds.find(imageId => {
const sopCommonModule = cornerstone.metaData.get( const sopCommonModule = cornerstone.metaData.get(
"sopCommonModule", 'sopCommonModule',
imageId imageId
); );
if (!sopCommonModule) { if (!sopCommonModule) {
return; return;
} }
return sopCommonModule.sopInstanceUID === ReferencedSOPInstanceUID; return sopCommonModule.sopInstanceUID === ReferencedSOPInstanceUID;
@@ -517,33 +612,32 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
continue; continue;
} }
const sourceImageMetadata = cornerstone.metaData.get( const sourceImageMetadata = cornerstone.metaData.get('instance', imageId);
"instance",
imageId
);
if ( if (
segMetadata.Rows !== sourceImageMetadata.Rows || segMetadata.Rows !== sourceImageMetadata.Rows ||
segMetadata.Columns !== sourceImageMetadata.Columns segMetadata.Columns !== sourceImageMetadata.Columns
) { ) {
const warningMessage = 'The segmentation ' + const warningMessage =
'The segmentation ' +
'has frames with different geometry ' + 'has frames with different geometry ' +
'dimensions (Rows and Columns) respect to the source images.'; 'dimensions (Rows and Columns) respect to the source images.';
inconsistencyWarnings.push(warningMessage); inconsistencyWarnings.push(warningMessage);
break; break;
} }
} }
if (inconsistencyWarnings.length !== 0) { if (inconsistencyWarnings.length !== 0) {
const warningMessage = 'The segmentation format is not supported yet. ' + const warningMessage =
'The segmentation format is not supported yet. ' +
'The segmentation data (segments) could not be loaded.'; 'The segmentation data (segments) could not be loaded.';
inconsistencyWarnings.push(warningMessage); inconsistencyWarnings.push(warningMessage);
} }
} }
// cache the warnings // cache the warnings
displaySet.inconsistencyWarnings = inconsistencyWarnings; displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings; return inconsistencyWarnings;
} };
/** /**
* Checks if display set is active, i.e. if the series is currently shown * Checks if display set is active, i.e. if the series is currently shown
@@ -562,30 +656,42 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
* @param {string} activeDisplaySetInstanceUID * @param {string} activeDisplaySetInstanceUID
* @returns {boolean} is active. * @returns {boolean} is active.
*/ */
const _isDisplaySetActive = function(displaySet, studies, activeDisplaySetInstanceUID) { const _isDisplaySetActive = function(
displaySet,
studies,
activeDisplaySetInstanceUID
) {
let active = false; let active = false;
const { const { displaySetInstanceUID } = displaySet;
displaySetInstanceUID,
} = displaySet;
// TO DO: in the future, we could possibly support new modalities // TO DO: in the future, we could possibly support new modalities
// we should have a list of all modalities here, instead of having hard coded checks // we should have a list of all modalities here, instead of having hard coded checks
if (displaySet.Modality !== 'SEG' && if (
displaySet.Modality !== 'SEG' &&
displaySet.Modality !== 'RTSTRUCT' && displaySet.Modality !== 'RTSTRUCT' &&
displaySet.Modality !== 'RTDOSE') { displaySet.Modality !== 'RTDOSE'
) {
active = activeDisplaySetInstanceUID === displaySetInstanceUID; active = activeDisplaySetInstanceUID === displaySetInstanceUID;
} else if (displaySet.getSourceDisplaySet){ } else if (displaySet.getSourceDisplaySet) {
if (displaySet.Modality === 'SEG') { if (displaySet.Modality === 'SEG') {
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false); const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
active = referencedDisplaySet ? studies,
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID : false
false; );
active = referencedDisplaySet
? activeDisplaySetInstanceUID ===
referencedDisplaySet.displaySetInstanceUID
: false;
} else { } else {
const referencedDisplaySet = displaySet.getSourceDisplaySet(studies, false); const referencedDisplaySet = displaySet.getSourceDisplaySet(
active = referencedDisplaySet ? studies,
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID : false
false; );
active = referencedDisplaySet
? activeDisplaySetInstanceUID ===
referencedDisplaySet.displaySetInstanceUID
: false;
} }
} }
@@ -609,7 +715,6 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
const { const {
displaySetInstanceUID, displaySetInstanceUID,
SeriesDescription, SeriesDescription,
InstanceNumber,
numImageFrames, numImageFrames,
SeriesNumber, SeriesNumber,
} = displaySet; } = displaySet;
@@ -629,19 +734,30 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
altImageText = displaySet.Modality ? displaySet.Modality : 'UN'; altImageText = displaySet.Modality ? displaySet.Modality : 'UN';
} }
const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet, studies); const hasWarnings = _checkForSeriesInconsistencesWarnings(
const active = _isDisplaySetActive(displaySet, studies, activeDisplaySetInstanceUID) displaySet,
studies
);
const hasDerivedDisplaySets = _checkForDerivedDisplaySets(
displaySet,
study
);
return { return {
active, active: _isDisplaySetActive(
displaySet,
studies,
activeDisplaySetInstanceUID
),
imageId, imageId,
altImageText, altImageText,
displaySetInstanceUID, displaySetInstanceUID,
SeriesDescription, SeriesDescription,
InstanceNumber,
numImageFrames, numImageFrames,
SeriesNumber, SeriesNumber,
hasWarnings, hasWarnings,
hasDerivedDisplaySets,
}; };
}); });
@@ -139,10 +139,11 @@ class ViewerMain extends Component {
displaySetInstanceUID displaySetInstanceUID
); );
const { LoggerService, UINotificationService } = servicesManager.services;
if (displaySet.isDerived) { if (displaySet.isDerived) {
const { Modality } = displaySet; const { Modality } = displaySet;
if (Modality === 'SEG' && servicesManager) { if (Modality === 'SEG' && servicesManager) {
const {LoggerService, UINotificationService} = servicesManager.services;
const onDisplaySetLoadFailureHandler = error => { const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message }); LoggerService.error({ error, message: error.message });
UINotificationService.show({ UINotificationService.show({
@@ -153,24 +154,41 @@ class ViewerMain extends Component {
}); });
}; };
const {referencedDisplaySet} = displaySet.getSourceDisplaySet( const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
this.props.studies, this.props.studies,
true, true,
onDisplaySetLoadFailureHandler onDisplaySetLoadFailureHandler
); );
displaySet = referencedDisplaySet; displaySet = referencedDisplaySet;
} else { } else {
displaySet = displaySet.getSourceDisplaySet(this.props.studies); displaySet = displaySet.getSourceDisplaySet(this.props.studies);
} }
if (!displaySet) { if (!displaySet) {
throw new Error( const error = new Error('Source data not present');
`Referenced series for ${Modality} dataset not present.` const message = 'Source data not present';
); LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
} }
} }
if (displaySet.isModalitySupported === false) {
const error = new Error('Modality not supported');
const message = 'Modality not supported';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
this.props.setViewportSpecificData(viewportIndex, displaySet); this.props.setViewportSpecificData(viewportIndex, displaySet);
}; };
+1
View File
@@ -0,0 +1 @@
3.7
Submodule
+1
Submodule testdata added at 47d51e15a0.
+1181 -222
View File
File diff suppressed because it is too large. Load diff