Compare commits

...

59 Commits

Author SHA1 Message Date
ohif-bot
29dcc82e28 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.2.5
 - @ohif/extension-dicom-microscopy@0.51.1
 - @ohif/extension-dicom-pdf@1.0.4
 - @ohif/extension-dicom-rt@0.4.1
 - @ohif/extension-dicom-segmentation@0.4.1
 - @ohif/extension-vtk@1.7.2
 - @ohif/core@2.10.1
 - @ohif/viewer@4.5.3
2020-08-28 15:52:27 +00:00
James Petts
dbf0501031
[IDC-1994] Sort series list by SeriesNumber, and sort by same SeriesNumber by date/time. (#2010)
* Sort based on SeriesNumber and SeriesDate/SeriesTime.

* Harden, and perform final sort in algorithm if last N entries have the same SeriesNumber.

* Switch to insertion rather than sorting as sorting is too slow. Reimplement low priority sorting into new insertion method.

* Fix local file viewing.
2020-08-28 16:51:08 +01:00
ohif-bot
ef28fa2ac1 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.1
 - @ohif/viewer@4.5.2
2020-08-24 09:45:34 +00:00
James Petts
779a7e0976
fix: 🐛 Fail gracefully on an MPR load error (#1992)
* feat: 🎸 Update react-vtkjs-viewport usage to use requestPool

* Fix import of react-vtkjs-viewport to cornerstone-tools path.

* Increase maximum load time of MPR test now we are throttling requests.

* fix: 🐛 Fail gracefully on an MPR load error

* Respond to reviewer comments.
2020-08-24 10:44:09 +01:00
ohif-bot
7f3ca1fc23 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.2
 - @ohif/viewer@4.5.1
2020-08-20 14:50:30 +00:00
Alex Broaddus
59ab68b4ed
fix: Updated react-cornerstone-viewport to version 4.0.2 (#2001)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-08-20 10:48:16 -04:00
ohif-bot
248bc0a64a chore(release): publish [skip ci]
- @ohif/viewer@4.5.0
2020-08-20 13:13:52 +00:00
ladeirarodolfo
5fdace1432
feat: 🎸 Filter/promote multiple series instances (#1533)
improve filter/promote to be applied on multiple series instances

 Closes: 1532

Co-authored-by: Rodolfo Ladeira <rodolfo_radical@hotmail.com>
2020-08-20 15:12:00 +02:00
ohif-bot
71b6b454a3 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.1
 - @ohif/viewer@4.4.1
2020-08-20 02:25:10 +00:00
Alex Broaddus
3b02a06f5e
perf(stackPrefetch): Added stackPrefetch config with 20 max concurrent requests (#2000)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-08-19 22:23:00 -04:00
Erik Ziegler
ebdcde1c4d
fix: Fix incorrect command name in Percy test (#1999) 2020-08-19 15:42:25 -04:00
Erik Ziegler
829ac2b410
fix: Avoid lerna:restore unless we are on Netlify (closes #1926, #1996) (#1997) 2020-08-19 10:58:54 +02:00
Erik Ziegler
09745a0349 wip 2020-08-19 10:41:58 +02:00
Erik Ziegler
cd73dd3027 fix: Avoid lerna:restore unless we are on Netlify (closes #1926, #1996) 2020-08-19 10:01:47 +02:00
ohif-bot
ca511edeff chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.0
 - @ohif/extension-dicom-rt@0.4.0
 - @ohif/extension-dicom-segmentation@0.4.0
 - @ohif/extension-vtk@1.7.0
 - @ohif/core@2.10.0
 - @ohif/viewer@4.4.0
2020-08-18 12:03:55 +00:00
James Petts
bb5f30ce2a
feat: 🎸 Update react-vtkjs-viewport usage to use requestPool (#1984)
* feat: 🎸 Update react-vtkjs-viewport usage to use requestPool

* Fix import of react-vtkjs-viewport to cornerstone-tools path.

* Increase maximum load time of MPR test now we are throttling requests.

* Remove debugger

Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2020-08-18 14:01:44 +02:00
ohif-bot
87d5c68e71 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.3.0
 - @ohif/viewer@4.3.1
2020-08-10 12:18:19 +00:00
James Petts
0e87ab37c2
feat: 🎸 Add support for POINT and OPEN_PLANAR for RT
* feat: 🎸 Add support for POINT and OPEN_PLANAR for RT
2020-08-10 13:16:19 +01:00
ohif-bot
756b005fe1 chore(release): publish [skip ci]
- @ohif/viewer@4.3.0
2020-08-10 09:42:47 +00:00
James Petts
3d86b5f876
feat: 🎸 Optional disable measurements panel in app config (#1912)
Can disable the measurements panel by adding disableMeasurementPanel:
true to the appConfig.

Closes: #1864
2020-08-10 10:40:11 +01:00
ohif-bot
2a647fd618 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.2.7
 - @ohif/extension-dicom-segmentation@0.3.4
 - @ohif/extension-vtk@1.6.10
 - @ohif/core@2.9.14
 - @ohif/viewer@4.2.16
2020-08-10 09:21:53 +00:00
James Petts
f8fc31bcac
[IDC-1670] Better display of derived datasets in side panel. (#1962)
* WIP

* WIP

* WIP

* WIP

* Working click on seg.

* Load SEG if not loaded and set active.

* RTSTRUCT

* feat: 🎸 Display SEG and RTSTRUCT in side panel

* Throw error if dataset not available.

* Add delay in microscopy viewer e2e test.

* Add delay in microscopy viewer e2e test.
2020-08-10 10:20:31 +01:00
ohif-bot
b7ab88b0f1 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.5
 - @ohif/viewer@4.2.15
2020-08-05 09:48:08 +00:00
James Petts
57a30d7c34
[IDC-1956] - Remove loading %age which is always zero. (#1959)
* Remove loading percent.

* fix: 🐛 Remove % loading on cornerstone viewport
2020-08-05 10:46:42 +01:00
ohif-bot
cd90665b0e chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.2.6
 - @ohif/viewer@4.2.14
2020-08-05 09:03:27 +00:00
James Petts
98c657231e
IDC-1897 (#1940)
* show warnings for rt.

* Remove unneeded old code
2020-08-05 10:00:47 +01:00
ohif-bot
14da247318 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.6.9
 - @ohif/ui@1.5.3
 - @ohif/viewer@4.2.13
2020-07-23 07:44:17 +00:00
Erik Ziegler
6a77d6c778
chore: Use same config for branch deploys as for deploy previews (#1893) (#1916) 2020-07-23 09:42:42 +02:00
Sinan
8f1785446b
docs: Update readme for end to end test commands (#1774) 2020-07-22 21:52:23 +02:00
ohif-bot
41225eefc0 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.6.8
 - @ohif/core@2.9.13
 - @ohif/ui@1.5.2
 - @ohif/viewer@4.2.12
2020-07-22 19:12:41 +00:00
Erik Ziegler
7772fee21a
fix: Switch DICOMFileUploader to use the UIModalService (#1904) 2020-07-22 21:10:04 +02:00
ohif-bot
d870ac914a chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.4
 - @ohif/extension-dicom-rt@0.2.5
 - @ohif/extension-vtk@1.6.7
 - @ohif/core@2.9.12
 - @ohif/viewer@4.2.11
2020-07-13 16:03:44 +00:00
James Petts
451f7eab92
fix: 🐛 - Put guards in all places that a cornerstone re-render (#1899)
* fix: 🐛 - Put guards in all places that a cornerstone re-render
2020-07-13 17:02:23 +01:00
ohif-bot
029f8af4c7 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.3
 - @ohif/extension-dicom-html@1.2.4
 - @ohif/extension-dicom-rt@0.2.4
 - @ohif/extension-dicom-segmentation@0.3.3
 - @ohif/extension-vtk@1.6.6
 - @ohif/core@2.9.11
 - @ohif/viewer@4.2.10
2020-07-13 15:37:54 +00:00
James Petts
b1b4c8c861
IDC 1856 - Update dcmjs version to prevent failed naturalization of some DICOM instances. (#1898)
* fix: 🐛 Fix RT Panel hide/show

* Prevent infinite loop by persisting load errors on displaySet.

* fix: 🐛 Fix infinite error messages

* Fix unit tests.

* WIP

* fix: 🐛 If no contours for structure, cleanly exit and notify

 Closes: #1892

* Update dcmjs to solve issue.

* fix: 🐛 Update dcmjs to solve end of file issue
2020-07-13 16:35:55 +01:00
ohif-bot
cdde2af224 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.2.3
 - @ohif/viewer@4.2.9
2020-07-13 14:30:47 +00:00
James Petts
4a2922dda0
Idc 1892 (#1896)
* fix: 🐛 Fix RT Panel hide/show

* Prevent infinite loop by persisting load errors on displaySet.

* fix: 🐛 Fix infinite error messages

* Fix unit tests.

* WIP

* fix: 🐛 If no contours for structure, cleanly exit and notify

 Closes: #1892
2020-07-13 15:29:29 +01:00
ohif-bot
b6b5a8414f chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.2.2
 - @ohif/extension-dicom-segmentation@0.3.2
 - @ohif/extension-vtk@1.6.5
 - @ohif/core@2.9.10
 - @ohif/viewer@4.2.8
2020-07-13 12:53:22 +00:00
James Petts
e7cc735c03
fix: 🐛 Fix RT Panel hide/show and Fix looping load errors (#1877)
* fix: 🐛 Fix RT Panel hide/show

* Prevent infinite loop by persisting load errors on displaySet.

* fix: 🐛 Fix infinite error messages

* Fix unit tests.

* comment out reamining MRSTUDY line
2020-07-13 13:51:33 +01:00
Danny Brown
6121bdcf63
Merge pull request #1828 from Aurelius333/patch-1
Fix README.md Quick Start Deployment instructions
2020-06-30 10:59:20 -04:00
Aurelius333
da70079675
Fix README.md Quick Start Deployment instructions
Change `window.OHIFStandaloneViewer` to `window.OHIFViewer`, since the former is undefined
2020-06-29 00:31:45 -04:00
ohif-bot
9089875ca7 chore(release): publish [skip ci]
- @ohif/extension-dicom-p10-downloader@0.1.2
 - @ohif/viewer@4.2.7
2020-06-18 09:01:18 +00:00
Rodrigo Antinarelli
b4627ecfa3
Feat/1746 idc dev tool (#1778)
* feat: Create downloadAndUploadStudy method

* create upload handler to store instances in another server

* refactor and create reusable method to download buffers

* fix: e2e studies amount searching by Modality

* fix destructuring

* fix dataset and log info

* split instances to send multiple storeInstances calls

* minor refactor progress

* fix e2e

* replace error message for upload

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-18 09:59:07 +01:00
ohif-bot
4ede8a525a chore(release): publish [skip ci]
- @ohif/extension-vtk@1.6.4
 - @ohif/core@2.9.9
 - @ohif/viewer@4.2.6
2020-06-18 07:28:48 +00:00
Rodrigo Antinarelli
2c23818c86
Fix: #1776 - Segmentations not loading (#1795)
* temp comments analyze

* fix: cache derivedDisplaySets

* replace const

* minor refactor

* fix e2e inconsistent value

* increase e2e timeout
2020-06-18 08:24:39 +01:00
ohif-bot
fa4b03d62d chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.2
 - @ohif/extension-dicom-html@1.2.3
 - @ohif/extension-dicom-rt@0.2.1
 - @ohif/extension-dicom-segmentation@0.3.1
 - @ohif/extension-vtk@1.6.3
 - @ohif/core@2.9.8
 - @ohif/viewer@4.2.5
2020-06-15 15:44:36 +00:00
Igor Octaviano
698e900b85
fix: 🐛 Disable seg panel when data for seg unavailable (#1732)
* Add disable state for invalid data

* fix: 🐛 Disable seg panel when data for seg unavailable

We need to gracefully handle errors if data for segmentation is
unavailable and disable the segmentation panel

 Closes: #1728

* Bump dcmjs version

dcmjs
  * @ohif/core: ^0.12.2 → ^0.13.0
  * @ohif/extension-cornerstone: ^0.12.3 → ^0.13.0
  * @ohif/extension-dicom-html: ^0.12.3 → ^0.13.0
  * @ohif/extension-dicom-rt: ^0.12.3 → ^0.13.0
  * @ohif/extension-dicom-segmentation: ^0.12.2 → ^0.13.0
  * @ohif/extension-vtk: ^0.12.2 → ^0.13.0
  * @ohif/viewer: ^0.12.2 → ^0.13.0

* Remove error barrier

* Fix e2e

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-15 16:41:36 +01:00
ohif-bot
b7d47383c5 chore(release): publish [skip ci]
- @ohif/viewer@4.2.4
2020-06-15 12:47:19 +00:00
Rodrigo Antinarelli
6123741765
fix: OIDC Redirect erases query parameters (#1773)
* fix: OIDC Redirect erases query parameters

* prevent issues

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-15 13:43:10 +01:00
ohif-bot
ab25be3520 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.6.2
 - @ohif/ui@1.5.1
 - @ohif/viewer@4.2.3
2020-06-05 16:03:56 +00:00
Rodrigo Antinarelli
950a54c704
Feat/1767 error boundary stack trace (#1772)
* feat: ErrorBoundary collapsed stack trace

* button styles

* Change "Toggle stack trace" to "Stack Trace"

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-05 17:00:36 +01:00
ohif-bot
4293c073f0 chore(release): publish [skip ci]
- @ohif/extension-dicom-p10-downloader@0.1.1
 - @ohif/extension-vtk@1.6.1
 - @ohif/core@2.9.7
 - @ohif/viewer@4.2.2
2020-06-04 11:02:53 +00:00
Emanuel Fiuza de Oliveira
62a2cd5ff3
Fix: #1130 Fixing support for download of an entire study (#1753)
Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-04 12:01:05 +01:00
ohif-bot
2d827c2a49 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.1
 - @ohif/viewer@4.2.1
2020-06-04 10:27:47 +00:00
Igor Octaviano
a8305772d0
fix: 🐛 Infinite frame index change when quickly jumping between (#1734)
* Remove debounce and use react-cornerstone-viewport native debounce time

* fix: 🐛 Infinite frame index change when quickly jumping between

This change removes the debounced callback function from the app and
instead uses the native debounce time from react-cornerstone-viewport.
This change allows the application to set its state in a more controled
way.

 Closes: #1733

* Bump rcv version

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-04 11:25:19 +01:00
ohif-bot
eab115e70c chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.8.0
 - @ohif/extension-dicom-microscopy@0.51.0
 - @ohif/extension-dicom-rt@0.2.0
 - @ohif/extension-dicom-segmentation@0.3.0
 - @ohif/extension-vtk@1.6.0
 - @ohif/ui@1.5.0
 - @ohif/viewer@4.2.0
2020-06-04 09:54:24 +00:00
Rodrigo Antinarelli
c02b232b0c
feat: 🎸 1729 - error boundary wrapper (#1764)
* Add error boundaries

* Fix PWA e2e.

* feat: ErrorBoundaryDialog

* replace component to use ErrorBoundaryDialog

* add proptypes

* fix context

* remove ErrorBoundary from extensions

Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
Co-authored-by: James A. Petts <jamesapetts@gmail.com>
2020-06-04 10:52:30 +01:00
ohif-bot
f01640d009 chore(release): publish [skip ci]
- @ohif/viewer@4.1.0
2020-05-15 18:59:23 +00:00
Steve Pieper
63fd65690c
feat: expose some app internals as window.app (#1735)
* feat: expose some app internals as window.app

This can help developers explore and access some
internal functionaltiy for debugging in the console.

For example, this command can download the currently
viewed study:

ohif.app.commandsManager.runCommand("downloadAndZip", {listOfUIDs: [window.location.href.split("/").pop()]})

TODO: collect this example and other handy functions on a wiki page

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>

* fix: typo in window.ohif.app assignment

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
2020-05-15 14:55:54 -04:00
107 changed files with 2572 additions and 823 deletions

View File

@ -11,6 +11,7 @@ node -v
# Install build deps and all monorepo package dependencies. Yarn Workspaces # Install build deps and all monorepo package dependencies. Yarn Workspaces
# should also symlink all projects appropriately # should also symlink all projects appropriately
yarn run lerna:restore
yarn install --no-ignore-optional --pure-lockfile yarn install --no-ignore-optional --pure-lockfile
# Build && Move PWA Output # Build && Move PWA Output
@ -21,6 +22,19 @@ mv platform/viewer/dist/* .netlify/www/pwa -v
# Build && Move script output # Build && Move script output
# yarn run build:package # yarn run build:package
# Build && Move Docz Output
# Using local yarn install to prevent Gatsby from needing to access
# node_modules above the platform/ui folder
cd platform/ui
yarn install
yarn run build
cd ../..
mkdir -p ./.netlify/www/ui
mv platform/ui/.docz/dist/* .netlify/www/ui -v
# Cache all of the node_module dependencies in
# extensions, modules, and platform packages
yarn run lerna:cache
echo 'Nothing left to see here. Go home, folks.' echo 'Nothing left to see here. Go home, folks.'
# Build using react-scripts # Build using react-scripts

View File

@ -122,7 +122,7 @@ window.config = {
``` ```
- Install the viewer: - Install the viewer:
`window.OHIFStandaloneViewer.installViewer(window.config);` `window.OHIFViewer.installViewer(window.config);`
This exact setup is demonstrated in this This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our [CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our

View File

@ -3,6 +3,99 @@
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.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.1...@ohif/extension-cornerstone@2.9.2) (2020-08-20)
### Bug Fixes
* Updated react-cornerstone-viewport to version 4.0.2 ([#2001](https://github.com/OHIF/Viewers/issues/2001)) ([59ab68b](https://github.com/OHIF/Viewers/commit/59ab68b4edbfec010170c4429c300a53f688bf94))
## [2.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.0...@ohif/extension-cornerstone@2.9.1) (2020-08-20)
### Performance Improvements
* **stackPrefetch:** Added stackPrefetch config with 20 max concurrent requests ([#2000](https://github.com/OHIF/Viewers/issues/2000)) ([3b02a06](https://github.com/OHIF/Viewers/commit/3b02a06f5e250660edde4862ce44147db3dc3ab9))
# [2.9.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.5...@ohif/extension-cornerstone@2.9.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [2.8.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.4...@ohif/extension-cornerstone@2.8.5) (2020-08-05)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.8.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.3...@ohif/extension-cornerstone@2.8.4) (2020-07-13)
### Bug Fixes
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
## [2.8.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.2...@ohif/extension-cornerstone@2.8.3) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.8.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.1...@ohif/extension-cornerstone@2.8.2) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.0...@ohif/extension-cornerstone@2.8.1) (2020-06-04)
### Bug Fixes
* 🐛 Infinite frame index change when quickly jumping between ([#1734](https://github.com/OHIF/Viewers/issues/1734)) ([a830577](https://github.com/OHIF/Viewers/commit/a8305772d0fbec506549ad7ea08504a8bc3a4f39)), closes [#1733](https://github.com/OHIF/Viewers/issues/1733)
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.3...@ohif/extension-cornerstone@2.8.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12) ## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.7.3", "version": "2.9.2",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@ -34,9 +34,9 @@
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.3", "dcmjs": "0.16.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
@ -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": "2.3.8" "react-cornerstone-viewport": "4.0.2"
} }
} }

View File

@ -4,7 +4,6 @@ import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import debounce from 'lodash.debounce';
const { StackManager } = OHIF.utils; const { StackManager } = OHIF.utils;
@ -216,9 +215,10 @@ class OHIFCornerstoneViewport extends Component {
}); });
} }
const debouncedNewImageHandler = debounce(({ currentImageIdIndex, sopInstanceUid }) => { const newImageHandler = ({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props.viewportData; const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet; const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) { if (currentImageIdIndex > 0) {
this.props.onNewImage({ this.props.onNewImage({
StudyInstanceUID, StudyInstanceUID,
@ -227,7 +227,7 @@ class OHIFCornerstoneViewport extends Component {
activeViewportIndex: viewportIndex, activeViewportIndex: viewportIndex,
}); });
} }
}, 700); };
return ( return (
<> <>
@ -235,7 +235,8 @@ class OHIFCornerstoneViewport extends Component {
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
imageIds={imageIds} imageIds={imageIds}
imageIdIndex={currentImageIdIndex} imageIdIndex={currentImageIdIndex}
onNewImage={debouncedNewImageHandler} onNewImage={newImageHandler}
onNewImageDebounceTime={700}
// ~~ Connected (From REDUX) // ~~ Connected (From REDUX)
// frameRate={frameRate} // frameRate={frameRate}
// isPlaying={false} // isPlaying={false}

View File

@ -10,6 +10,14 @@ const scroll = cornerstoneTools.import('util/scroll');
const { studyMetadataManager } = OHIF.utils; const { studyMetadataManager } = OHIF.utils;
const { setViewportSpecificData } = OHIF.redux.actions; const { setViewportSpecificData } = OHIF.redux.actions;
const refreshCornerstoneViewports = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
});
};
const commandsModule = ({ servicesManager }) => { const commandsModule = ({ servicesManager }) => {
const actions = { const actions = {
rotateViewport: ({ viewports, rotation }) => { rotateViewport: ({ viewports, rotation }) => {
@ -180,10 +188,7 @@ const commandsModule = ({ servicesManager }) => {
measurementApi.syncMeasurementsAndToolData(); measurementApi.syncMeasurementsAndToolData();
// Update images in all active viewports refreshCornerstoneViewports();
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
}, },
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) { getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
const nearbyTool = {}; const nearbyTool = {};
@ -262,23 +267,26 @@ const commandsModule = ({ servicesManager }) => {
StudyInstanceUID, StudyInstanceUID,
SOPInstanceUID, SOPInstanceUID,
frameIndex, frameIndex,
activeViewportIndex activeViewportIndex,
}) => { }) => {
const study = studyMetadataManager.get(StudyInstanceUID); const study = studyMetadataManager.get(StudyInstanceUID);
const displaySet = study.findDisplaySet(ds => { const displaySet = study.findDisplaySet(ds => {
return ds.images && ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID) return (
ds.images &&
ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
);
}); });
displaySet.SOPInstanceUID = SOPInstanceUID; displaySet.SOPInstanceUID = SOPInstanceUID;
displaySet.frameIndex = frameIndex; displaySet.frameIndex = frameIndex;
window.store.dispatch(setViewportSpecificData(activeViewportIndex, displaySet)); window.store.dispatch(
setViewportSpecificData(activeViewportIndex, displaySet)
);
cornerstone.getEnabledElements().forEach(enabledElement => { refreshCornerstoneViewports();
cornerstone.updateImage(enabledElement.element); },
});
}
}; };
const definitions = { const definitions = {

View File

@ -39,7 +39,9 @@ export default {
const onNewImageHandler = jumpData => { const onNewImageHandler = jumpData => {
commandsManager.runCommand('jumpToImage', jumpData); commandsManager.runCommand('jumpToImage', jumpData);
}; };
return <OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />; return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
);
}; };
return ExtendedOHIFCornerstoneViewport; return ExtendedOHIFCornerstoneViewport;

View File

@ -34,4 +34,11 @@ export default function(configuration = {}) {
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)'); cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
cornerstoneTools.store.state.touchProximity = 40; cornerstoneTools.store.state.touchProximity = 40;
// Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
});
} }

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.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.4...@ohif/extension-dicom-html@1.2.5) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.3...@ohif/extension-dicom-html@1.2.4) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.2...@ohif/extension-dicom-html@1.2.3) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.1...@ohif/extension-dicom-html@1.2.2) (2020-05-04) ## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.1...@ohif/extension-dicom-html@1.2.2) (2020-05-04)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.2.2", "version": "1.2.5",
"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.12.3", "dcmjs": "0.16.0",
"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"

View File

@ -22,6 +22,12 @@ const OHIFDicomHtmlSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance(); const instance = series.getFirstInstance();
const {
SeriesDate,
SeriesTime,
SeriesNumber,
} = instance._instance.metadata;
return { return {
plugin: 'html', plugin: 'html',
Modality: 'SR', Modality: 'SR',
@ -31,6 +37,9 @@ const OHIFDicomHtmlSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(), SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDate,
SeriesTime,
SeriesNumber,
authorizationHeaders, authorizationHeaders,
}; };
}, },

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.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.0...@ohif/extension-dicom-microscopy@0.51.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02) ## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy **Note:** Version bump only for package @ohif/extension-dicom-microscopy

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "0.50.9", "version": "0.51.1",
"description": "OHIF extension for Dicom Microscopy", "description": "OHIF extension for Dicom Microscopy",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -12,6 +12,12 @@ const DicomMicroscopySopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient) { getDisplaySetFromSeries(series, study, dicomWebClient) {
const instance = series.getFirstInstance(); const instance = series.getFirstInstance();
const {
ContentDate,
ContentTime,
SeriesNumber,
} = instance._instance.metadata;
// Note: We are passing the dicomweb client into each viewport! // Note: We are passing the dicomweb client into each viewport!
return { return {
@ -22,6 +28,9 @@ const DicomMicroscopySopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(), SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
}; };
}, },
}; };

View File

@ -92,8 +92,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? ( {this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2> <h2>{JSON.stringify(this.state.error)}</h2>
) : ( ) : (
<div style={style} ref={this.container} /> <div style={style} ref={this.container} />
)} )}
</div> </div>
); );
} }

View File

@ -3,6 +3,22 @@
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.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.0...@ohif/extension-dicom-p10-downloader@0.1.1) (2020-06-04)
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.0.2...@ohif/extension-dicom-p10-downloader@0.1.0) (2020-04-23) # [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.0.2...@ohif/extension-dicom-p10-downloader@0.1.0) (2020-04-23)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-p10-downloader", "name": "@ohif/extension-dicom-p10-downloader",
"version": "0.1.0", "version": "0.1.2",
"description": "OHIF extension for downloading DICOM P10 files", "description": "OHIF extension for downloading DICOM P10 files",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -1,16 +1,16 @@
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import { import {
save, save,
upload,
getDicomWebClientFromContext, getDicomWebClientFromContext,
getStudyInstanceUIDFromStudies,
getSOPInstanceReferenceFromActiveViewport, getSOPInstanceReferenceFromActiveViewport,
getSOPInstanceReferencesFromViewports, getSOPInstanceReferencesFromViewports,
} from './utils'; } from './utils';
import _downloadAndZip from './downloadAndZip'; import _downloadAndZip, { downloadInstances } from './downloadAndZip';
const { const {
utils: { utils: { Queue },
Queue,
},
} = OHIF; } = OHIF;
export function getCommands(context) { export function getCommands(context) {
@ -41,6 +41,14 @@ export function getCommands(context) {
listOfUIDs listOfUIDs
); );
}, },
downloadAndZipStudy({ servers, studies, progress }) {
const dicomWebClient = getDicomWebClientFromContext(context, servers);
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
return save(
_downloadAndZip(dicomWebClient, listOfUIDs, { progress }),
listOfUIDs
);
},
downloadAndZipSeriesOnViewports({ servers, viewports, progress }) { downloadAndZipSeriesOnViewports({ servers, viewports, progress }) {
const dicomWebClient = getDicomWebClientFromContext(context, servers); const dicomWebClient = getDicomWebClientFromContext(context, servers);
const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports); const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports);
@ -57,6 +65,28 @@ export function getCommands(context) {
listOfUIDs listOfUIDs
); );
}, },
downloadAndUploadStudy({ servers, studies, progress, serverConfig }) {
const dicomWebClient = getDicomWebClientFromContext(context, servers);
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
return upload(
downloadInstances(dicomWebClient, listOfUIDs, { progress }),
/**
* serverConfig is an object with the values used to create a new
* instance of DICOMwebClient.
*
* Basic Structure:
*
* const config = {
* url,
* headers,
* errorInterceptor
* }
*
* const dicomWeb = new api.DICOMwebClient(config);
*/
serverConfig
);
},
}; };
const definitions = { const definitions = {
@ -64,6 +94,11 @@ export function getCommands(context) {
commandFn: queue.bindSafe(actions.downloadAndZip, error), commandFn: queue.bindSafe(actions.downloadAndZip, error),
storeContexts: ['servers'], storeContexts: ['servers'],
}, },
downloadAndZipStudy: {
commandFn: queue.bindSafe(actions.downloadAndZipStudy, error),
storeContexts: ['servers', 'studies'],
options: { progress },
},
downloadAndZipSeriesOnViewports: { downloadAndZipSeriesOnViewports: {
commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error), commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error),
storeContexts: ['servers', 'viewports'], storeContexts: ['servers', 'viewports'],
@ -77,6 +112,11 @@ export function getCommands(context) {
storeContexts: ['servers', 'viewports'], storeContexts: ['servers', 'viewports'],
options: { progress }, options: { progress },
}, },
downloadAndUploadStudy: {
commandFn: queue.bindSafe(actions.downloadAndUploadStudy, error),
storeContexts: ['servers', 'studies'],
options: { progress },
},
}; };
return { return {

View File

@ -79,13 +79,7 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
if (dicomWebClient instanceof api.DICOMwebClient) { if (dicomWebClient instanceof api.DICOMwebClient) {
const settings = buildSettings(listOfUIDs, options); const settings = buildSettings(listOfUIDs, options);
const { compression } = settings.tasks; const { compression } = settings.tasks;
// Register user-provided progress handler as a task list observer const buffers = await downloadBuffers(settings, dicomWebClient);
progressUtils.addObserver(settings.taskList, settings.options.progress);
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
// Reject promise from compression task on download failure
compression.deferred.reject(error);
throw error;
});
compression.deferred.resolve(zipAll(buffers, settings)); compression.deferred.resolve(zipAll(buffers, settings));
const url = await compression.deferred.promise; const url = await compression.deferred.promise;
return url; return url;
@ -93,6 +87,27 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
throw new Error('A valid DICOM Web Client instance is expected'); throw new Error('A valid DICOM Web Client instance is expected');
} }
async function downloadInstances(dicomWebClient, listOfUIDs, options) {
if (dicomWebClient instanceof api.DICOMwebClient) {
const settings = buildSettings(listOfUIDs, options);
const buffers = await downloadBuffers(settings, dicomWebClient);
return buffers;
}
throw new Error('A valid DICOM Web Client instance is expected');
}
async function downloadBuffers(settings, dicomWebClient) {
const { compression } = settings.tasks;
// Register user-provided progress handler as a task list observer
progressUtils.addObserver(settings.taskList, settings.options.progress);
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
// Reject promise from compression task on download failure
compression.deferred.reject(error);
throw error;
});
return buffers;
}
/** /**
* Utils * Utils
*/ */
@ -241,4 +256,4 @@ async function download(
* Exports * Exports
*/ */
export { downloadAndZip as default, downloadAndZip }; export { downloadAndZip as default, downloadAndZip, downloadInstances };

View File

@ -100,11 +100,59 @@ function save(promise, listOfUIDs) {
}); });
} }
function upload(promise, serverConfig) {
return Promise.resolve(promise)
.then(async instances => {
const instancesAmount = instances.length;
OHIF.log.info(`Uploading study to ${serverConfig.url}`);
OHIF.log.info(
`${instancesAmount} instances are being uploaded. Don't close your browser.`
);
try {
const dicomWeb = new api.DICOMwebClient(serverConfig);
let progress = 0;
const getProgress = () => {
return ((progress * 100) / instancesAmount).toFixed();
};
for (const instance of instances) {
const options = {
datasets: [instance],
};
await dicomWeb.storeInstances(options);
progress++;
OHIF.log.info(`Progress: ${getProgress()}%`);
}
OHIF.log.info('Successfully uploaded!');
} catch (error) {
OHIF.log.error(`Failed to upload: ${error}`);
}
return instances;
})
.catch(error => {
OHIF.log.error(`Failed to upload: ${error}`);
return null;
});
}
function getStudyInstanceUIDFromStudies(studies) {
return Object.keys(Object(Object(studies).studyData)).slice(0, 1);
}
export { export {
save, save,
upload,
validDicomUid, validDicomUid,
getDicomWebClientFromConfig, getDicomWebClientFromConfig,
getDicomWebClientFromContext, getDicomWebClientFromContext,
getStudyInstanceUIDFromStudies,
getSOPInstanceReferenceFromActiveViewport, getSOPInstanceReferenceFromActiveViewport,
getSOPInstanceReferencesFromViewports, getSOPInstanceReferencesFromViewports,
}; };

View File

@ -3,6 +3,14 @@
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.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.3...@ohif/extension-dicom-pdf@1.0.4) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02) ## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-pdf **Note:** Version bump only for package @ohif/extension-dicom-pdf

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-pdf", "name": "@ohif/extension-dicom-pdf",
"version": "1.0.3", "version": "1.0.4",
"description": "OHIF extension for Dicom PDF", "description": "OHIF extension for Dicom PDF",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -12,6 +12,12 @@ const OHIFDicomPDFSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) { getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance(); const instance = series.getFirstInstance();
const {
ContentDate,
ContentTime,
SeriesNumber,
} = instance._instance.metadata;
return { return {
plugin: 'pdf', plugin: 'pdf',
Modality: 'DOC', Modality: 'DOC',
@ -21,6 +27,9 @@ const OHIFDicomPDFSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(), SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(), SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(), StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
authorizationHeaders: authorizationHeaders, authorizationHeaders: authorizationHeaders,
}; };
}, },

View File

@ -3,6 +3,112 @@
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.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.0...@ohif/extension-dicom-rt@0.4.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-rt
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.3.0...@ohif/extension-dicom-rt@0.4.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.7...@ohif/extension-dicom-rt@0.3.0) (2020-08-10)
### Features
* 🎸 Add support for POINT and OPEN_PLANAR for RT ([0e87ab3](https://github.com/OHIF/Viewers/commit/0e87ab37c29fcf9af74bbcefca854c6e6b8707bc))
## [0.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.6...@ohif/extension-dicom-rt@0.2.7) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.5...@ohif/extension-dicom-rt@0.2.6) (2020-08-05)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.4...@ohif/extension-dicom-rt@0.2.5) (2020-07-13)
### Bug Fixes
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.3...@ohif/extension-dicom-rt@0.2.4) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.2...@ohif/extension-dicom-rt@0.2.3) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.1...@ohif/extension-dicom-rt@0.2.2) (2020-07-13)
### Bug Fixes
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.0...@ohif/extension-dicom-rt@0.2.1) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.4...@ohif/extension-dicom-rt@0.2.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.3...@ohif/extension-dicom-rt@0.1.4) (2020-05-14) ## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.3...@ohif/extension-dicom-rt@0.1.4) (2020-05-14)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.1.4", "version": "0.4.1",
"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",
@ -30,8 +30,8 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "^0.12.3", "dcmjs": "0.16.0",
"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"

View File

@ -1,5 +1,6 @@
import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core'; import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core';
import loadRTStruct from './loadRTStruct'; import loadRTStruct from './loadRTStruct';
import getSourceDisplaySet from './getSourceDisplaySet';
import id from './id'; import id from './id';
@ -14,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
id: 'OHIFDicomRTStructSopClassHandler', id: 'OHIFDicomRTStructSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER, type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs, sopClassUIDs,
getDisplaySetFromSeries: function ( getDisplaySetFromSeries: function(
series, series,
study, study,
dicomWebClient, dicomWebClient,
@ -26,6 +27,7 @@ const OHIFDicomRTStructSopClassHandler = {
const { const {
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber,
SeriesDescription, SeriesDescription,
FrameOfReferenceUID, FrameOfReferenceUID,
SOPInstanceUID, SOPInstanceUID,
@ -52,6 +54,7 @@ const OHIFDicomRTStructSopClassHandler = {
isLoaded: false, isLoaded: false,
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber,
SeriesDescription, SeriesDescription,
}; };
@ -67,13 +70,18 @@ const OHIFDicomRTStructSopClassHandler = {
} }
} }
rtStructDisplaySet.load = function (referencedDisplaySet, studies) { rtStructDisplaySet.getSourceDisplaySet = function(studies) {
return getSourceDisplaySet(studies, rtStructDisplaySet);
};
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
return loadRTStruct( return loadRTStruct(
rtStructDisplaySet, rtStructDisplaySet,
referencedDisplaySet, referencedDisplaySet,
studies studies
).catch(error => { ).catch(error => {
rtStructDisplaySet.isLoaded = false; rtStructDisplaySet.isLoaded = false;
rtStructDisplaySet.loadError = true;
throw new Error(error); throw new Error(error);
}); });
}; };

View File

@ -17,7 +17,9 @@ const { studyMetadataManager } = utils;
const refreshViewport = () => { const refreshViewport = () => {
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element); if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
}); });
}; };
@ -31,7 +33,18 @@ const refreshViewport = () => {
* @param {number} props.isOpen - isOpen * @param {number} props.isOpen - isOpen
* @returns component * @returns component
*/ */
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => { const RTPanel = ({
studies,
viewports,
activeIndex,
isOpen,
onContourItemClick,
activeContexts = [],
contexts = {},
}) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
const [selectedContour, setSelectedContour] = useState(); const [selectedContour, setSelectedContour] = useState();
const DEFAULT_SET_INDEX = 0; const DEFAULT_SET_INDEX = 0;
const DEFAULT_STATE = { const DEFAULT_STATE = {
@ -65,7 +78,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets); document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => { return () => {
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets); document.removeEventListener(
'extensiondicomrtrtloaded',
updateStructureSets
);
}; };
}, []); }, []);
@ -78,7 +94,9 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
activeViewport.SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID); const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({ const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID, referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT', Modality: 'RTSTRUCT',
@ -89,7 +107,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
setState({ setState({
referencedDisplaysets, referencedDisplaysets,
selectedSet: defaultSet, selectedSet: defaultSet,
sets: viewportSets sets: viewportSets,
}); });
} else { } else {
setState(DEFAULT_STATE); setState(DEFAULT_STATE);
@ -105,7 +123,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
setShowSettings(showSettings && !isOpen); setShowSettings(showSettings && !isOpen);
}, [isOpen]); }, [isOpen]);
const toContourItem = ({ ROINumber, ROIName, RTROIObservations, colorArray, visible }, loadedSet) => { const toContourItem = (
{ ROINumber, ROIName, RTROIObservations, colorArray, visible, isSupported },
loadedSet
) => {
let interpretedType = ''; let interpretedType = '';
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) { if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`; interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
@ -116,36 +137,45 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
<StructureSetItem <StructureSetItem
key={ROINumber} key={ROINumber}
selected={isSameContour} selected={isSameContour}
isDisabled={!isSupported}
onClick={() => { onClick={() => {
setSelectedContour(isSameContour ? null : ROINumber); setSelectedContour(isSameContour ? null : ROINumber);
const enabledElements = cornerstone.getEnabledElements(); if (isCornerstone()) {
const element = enabledElements[activeIndex].element; const enabledElements = cornerstone.getEnabledElements();
const toolState = cornerstoneTools.getToolState(element, 'stack'); const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
if (!toolState) { if (!toolState) {
return; return;
}
const imageIds = toolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber,
imageIds
);
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
onContourItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
} }
const imageIds = toolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber,
imageIds
);
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
onContourItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex
});
}} }}
label={`${ROIName} ${interpretedType}`} label={`${ROIName} ${interpretedType}`}
index={ROINumber} index={ROINumber}
@ -153,7 +183,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
visible={visible} visible={visible}
onVisibilityChange={() => { onVisibilityChange={() => {
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
module.setters.toggleROIContour(state.selectedSet.SeriesInstanceUID, ROINumber); module.setters.toggleROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber
);
}} }}
/> />
); );
@ -189,49 +222,70 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
onClick={() => setShowSettings(true)} onClick={() => setShowSettings(true)}
/> />
</div> </div>
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />} {!state.referencedDisplaysets.length && (
{state.sets && state.referencedDisplaysets.map(displaySet => { <LoadingIndicator expand height="70px" width="70px" />
const { SeriesInstanceUID, metadata, isLoaded } = displaySet; )}
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID); const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
);
const loadedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID); const loadedSet = sets.find(
return ( set => set.SeriesInstanceUID === SeriesInstanceUID
<PanelSection );
key={SeriesInstanceUID} return (
title={metadata.StructureSetLabel} <PanelSection
loading={!isLoaded || !loadedSet} key={SeriesInstanceUID}
visible={isLoaded && loadedSet.visible} title={metadata.StructureSetLabel}
hideVisibleButton={!isLoaded} loading={!isLoaded || !loadedSet}
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID} visible={isLoaded && loadedSet && loadedSet.visible}
onVisibilityChange={newVisibility => { hideVisibleButton={!isLoaded}
const module = cornerstoneTools.getModule('rtstruct'); expanded={
loadedSet.ROIContours.forEach(({ ROINumber }) => { isLoaded &&
module.setters.toggleROIContour(loadedSet.SeriesInstanceUID, ROINumber); loadedSet.SeriesInstanceUID ===
}); state.selectedSet.SeriesInstanceUID
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(viewports[activeIndex], studies);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
const selectedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
setState(state => ({ ...state, selectedSet, sets }));
} }
}} onVisibilityChange={newVisibility => {
> const module = cornerstoneTools.getModule('rtstruct');
<ScrollableArea>
<TableList headless> if (newVisibility) {
{isLoaded && loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))} module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
</TableList> } else {
</ScrollableArea> module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
</PanelSection> }
); const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
})} viewports[activeIndex].SeriesInstanceUID
);
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(viewports[activeIndex], studies);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
);
const selectedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
setState(state => ({ ...state, selectedSet, sets }));
}
}}
>
<ScrollableArea>
<TableList headless>
{isLoaded &&
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
</div> </div>
); );
}; };

View File

@ -22,11 +22,6 @@
width: 100%; width: 100%;
} }
.dcmrt-structure-set-item.selected .item-actions {
height: 35px;
visibility: visible;
}
.dcmrt-structure-set-item .item-actions { .dcmrt-structure-set-item .item-actions {
margin-left: -1px; margin-left: -1px;
background-color: var(--ui-gray-darker); background-color: var(--ui-gray-darker);
@ -47,7 +42,8 @@
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.dcmrt-structure-set-item .item-actions .btnAction:hover, .dcmrt-structure-set-item .item-actions .btnAction:active { .dcmrt-structure-set-item .item-actions .btnAction:hover,
.dcmrt-structure-set-item .item-actions .btnAction:active {
color: var(--text-primary-color); color: var(--text-primary-color);
} }
@ -86,3 +82,12 @@
.dcmrt-structure-set-item .item-label .eye-icon.--visible { .dcmrt-structure-set-item .item-label .eye-icon.--visible {
color: var(--default-color); color: var(--default-color);
} }
.dcmrt-structure-set-item.isDisabled .item-color-section {
background-color: #e29e4a;
color: #fff;
}
.dcmrt-structure-set-item.isDisabled .item-label {
color: var(--text-disabled-color);
}

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui'; import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
import './StructureSetItem.css'; import './StructureSetItem.css';
@ -20,6 +20,7 @@ ColoredCircle.propTypes = {
const StructureSetItem = ({ const StructureSetItem = ({
index, index,
label, label,
isDisabled,
onClick, onClick,
itemClass, itemClass,
color, color,
@ -31,22 +32,44 @@ const StructureSetItem = ({
useEffect(() => { useEffect(() => {
setIsVisible(visible); setIsVisible(visible);
}, [visible]) }, [visible]);
return ( let dcmrtClassNames = `dcmrt-structure-set-item`;
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
<TableListItem if (selected) {
key={index} dcmrtClassNames += ' selected';
itemKey={index} }
itemIndex={index}
itemClass={itemClass} if (isDisabled) {
itemMeta={<ColoredCircle color={color} />} dcmrtClassNames += ' isDisabled';
itemMetaClass="item-color-section" }
onItemClick={onClick}
> const warningIcon = (
<div> <span>
<div className="item-label" style={{ marginBottom: 4 }}> <Icon name="exclamation-triangle" />
<span>{label}</span> </span>
);
const tableListItem = (
<TableListItem
key={index}
itemKey={index}
itemIndex={index}
itemClass={itemClass}
itemMeta={isDisabled ? warningIcon : <ColoredCircle color={color} />}
itemMetaClass="item-color-section"
onItemClick={() => {
if (isDisabled) {
return;
}
onClick();
}}
>
<div>
<div className="item-label" style={{ marginBottom: 4 }}>
<span>{label}</span>
{!isDisabled && (
<Icon <Icon
className={`eye-icon ${isVisible && '--visible'}`} className={`eye-icon ${isVisible && '--visible'}`}
name={isVisible ? 'eye' : 'eye-closed'} name={isVisible ? 'eye' : 'eye-closed'}
@ -54,37 +77,72 @@ const StructureSetItem = ({
height="20px" height="20px"
onClick={event => { onClick={event => {
event.stopPropagation(); event.stopPropagation();
if (isDisabled) {
return;
}
const newVisibility = !isVisible; const newVisibility = !isVisible;
setIsVisible(newVisibility); setIsVisible(newVisibility);
onVisibilityChange(newVisibility); onVisibilityChange(newVisibility);
}} }}
/> />
</div>
{false && <div className="item-info">{'...'}</div>}
{false && (
<div className="item-actions">
<button
className="btnAction"
onClick={() => console.log('Relabelling...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Relabel
</button>
<button
className="btnAction"
onClick={() => console.log('Editing description...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Description
</button>
</div>
)} )}
</div> </div>
</TableListItem>
{false && <div className="item-info">{'...'}</div>}
{false && (
<div className="item-actions">
<button
className="btnAction"
onClick={() => console.log('Relabelling...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Relabel
</button>
<button
className="btnAction"
onClick={() => console.log('Editing description...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Description
</button>
</div>
)}
</div>
</TableListItem>
);
return (
<div className={dcmrtClassNames}>
<React.Fragment>
{isDisabled ? (
<OverlayTrigger
key={index}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Unsupported Region</div>
<div className="warningContent">
Contour type currently unsupported.
</div>
</Tooltip>
}
>
<div>{tableListItem}</div>
</OverlayTrigger>
) : (
<React.Fragment>{tableListItem} </React.Fragment>
)}
</React.Fragment>
</div> </div>
); );
}; };
@ -99,7 +157,7 @@ StructureSetItem.propTypes = {
StructureSetItem.defaultProps = { StructureSetItem.defaultProps = {
itemClass: '', itemClass: '',
onClick: () => { }, onClick: () => {},
}; };
export default StructureSetItem; export default StructureSetItem;

View File

@ -0,0 +1,38 @@
export default function getSourceDisplaySet(studies, rtStructDisplaySet) {
const referencedDisplaySet = _getReferencedDisplaySet(
rtStructDisplaySet,
studies
);
rtStructDisplaySet.load(referencedDisplaySet, studies);
return referencedDisplaySet;
}
const _getReferencedDisplaySet = (rtStructDisplaySet, studies) => {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== rtStructDisplaySet.displaySetInstanceUID
);
const ReferencedSeriesSequence = Array.isArray(
rtStructDisplaySet.metadata.ReferencedSeriesSequence
)
? rtStructDisplaySet.metadata.ReferencedSeriesSequence
: [rtStructDisplaySet.metadata.ReferencedSeriesSequence];
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
return referencedDisplaySet;
};

View File

@ -20,14 +20,21 @@ export default {
preRegistration({ servicesManager, configuration = {} }) { preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration }); init({ servicesManager, configuration });
}, },
getPanelModule({ commandsManager }) { getPanelModule({ commandsManager, servicesManager, api }) {
const ExtendedRTPanel = props => { const ExtendedRTPanel = props => {
const { activeContexts } = api.hooks.useAppContext();
const contourItemClickHandler = contourData => { const contourItemClickHandler = contourData => {
commandsManager.runCommand('jumpToImage', contourData); commandsManager.runCommand('jumpToImage', contourData);
}; };
return ( return (
<RTPanel {...props} onContourItemClick={contourItemClickHandler} /> <RTPanel
{...props}
onContourItemClick={contourItemClickHandler}
activeContexts={activeContexts}
contexts={api.contexts}
/>
); );
}; };

View File

@ -16,7 +16,6 @@ export default async function loadRTStruct(
studies studies
) { ) {
const rtStructModule = cornerstoneTools.getModule('rtstruct'); const rtStructModule = cornerstoneTools.getModule('rtstruct');
// Set here is loading is asynchronous. // Set here is loading is asynchronous.
// If this function throws its set back to false. // If this function throws its set back to false.
rtStructDisplaySet.isLoaded = true; rtStructDisplaySet.isLoaded = true;
@ -68,7 +67,6 @@ export default async function loadRTStruct(
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL; const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
for (let i = 0; i < ROIContourSequence.length; i++) { for (let i = 0; i < ROIContourSequence.length; i++) {
const ROIContour = ROIContourSequence[i]; const ROIContour = ROIContourSequence[i];
const { ReferencedROINumber, ContourSequence } = ROIContour; const { ReferencedROINumber, ContourSequence } = ROIContour;
@ -76,26 +74,17 @@ export default async function loadRTStruct(
continue; continue;
} }
_setROIContourMetadata( const isSupported = false;
structureSet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour
);
for (let c = 0; c < ContourSequence.length; c++) { const ContourSequenceArray = _toArray(ContourSequence);
for (let c = 0; c < ContourSequenceArray.length; c++) {
const { const {
ContourImageSequence, ContourImageSequence,
ContourData, ContourData,
NumberOfContourPoints, NumberOfContourPoints,
ContourGeometricType, ContourGeometricType,
} = ContourSequence[c]; } = ContourSequenceArray[c];
if (ContourGeometricType !== 'CLOSED_PLANAR') {
// TODO: Do we want to visualise types other than closed planar?
// We could easily do open planar.
continue;
}
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID; const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
@ -107,27 +96,47 @@ export default async function loadRTStruct(
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId); const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
const points = []; const points = [];
let measurementData;
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) { switch (ContourGeometricType) {
points.push({ case 'CLOSED_PLANAR':
x: ContourData[p], case 'OPEN_PLANAR':
y: ContourData[p + 1], case 'POINT':
z: ContourData[p + 2], isSupported = true;
});
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
points.push({
x: ContourData[p],
y: ContourData[p + 1],
z: ContourData[p + 2],
});
}
transformPointsToImagePlane(points, imagePlane);
measurementData = {
handles: {
points,
},
type: ContourGeometricType,
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
ROINumber: ReferencedROINumber,
};
imageIdSpecificToolData.push(measurementData);
break;
default:
continue;
} }
transformPointsToImagePlane(points, imagePlane);
const measurementData = {
handles: {
points,
},
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
ROINumber: ReferencedROINumber,
};
imageIdSpecificToolData.push(measurementData);
} }
_setROIContourMetadata(
structureSet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour,
isSupported
);
} }
_setToolEnabledIfNotEnabled(rtStructDisplayToolName); _setToolEnabledIfNotEnabled(rtStructDisplayToolName);
@ -157,7 +166,8 @@ function _setROIContourMetadata(
structureSet, structureSet,
StructureSetROISequence, StructureSetROISequence,
RTROIObservationsSequence, RTROIObservationsSequence,
ROIContour ROIContour,
isSupported
) { ) {
const StructureSetROI = StructureSetROISequence.find( const StructureSetROI = StructureSetROISequence.find(
structureSetROI => structureSetROI =>
@ -169,6 +179,7 @@ function _setROIContourMetadata(
ROIName: StructureSetROI.ROIName, ROIName: StructureSetROI.ROIName,
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm, ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
ROIDescription: StructureSetROI.ROIDescription, ROIDescription: StructureSetROI.ROIDescription,
isSupported,
visible: true, visible: true,
}; };
@ -238,7 +249,7 @@ function _setROIContourRTROIObservations(
function _setToolEnabledIfNotEnabled(toolName) { function _setToolEnabledIfNotEnabled(toolName) {
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
const { element } = enabledElement; const { element, image } = enabledElement;
const tool = cornerstoneTools.getToolForElement(element, toolName); const tool = cornerstoneTools.getToolForElement(element, toolName);
if (tool.mode !== 'enabled') { if (tool.mode !== 'enabled') {
@ -246,7 +257,9 @@ function _setToolEnabledIfNotEnabled(toolName) {
cornerstoneTools.setToolEnabled(toolName); cornerstoneTools.setToolEnabled(toolName);
} }
cornerstone.updateImage(element); if (image) {
cornerstone.updateImage(element);
}
}); });
} }
@ -307,3 +320,7 @@ function _getImageIdSopInstanceUidPairsForDisplaySet(
}; };
}); });
} }
function _toArray(objOrArray) {
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
}

View File

@ -4,6 +4,7 @@ import TOOL_NAMES from '../utils/toolNames';
// Cornerstone 3rd party dev kit imports // Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw'); const draw = importInternal('drawing/draw');
const drawCircle = importInternal('drawing/drawCircle');
const drawJoinedLines = importInternal('drawing/drawJoinedLines'); const drawJoinedLines = importInternal('drawing/drawJoinedLines');
const getNewContext = importInternal('drawing/getNewContext'); const getNewContext = importInternal('drawing/getNewContext');
const BaseTool = importInternal('base/BaseTool'); const BaseTool = importInternal('base/BaseTool');
@ -72,22 +73,52 @@ export default class RTStructDisplayTool extends BaseTool {
const colorArray = ROIContourData.colorArray; const colorArray = ROIContourData.colorArray;
const color = `rgba(${colorArray[0]},${colorArray[1]},${ const color = `rgba(${colorArray[0]},${colorArray[1]},${
colorArray[2] colorArray[2]
},${opacity})`; },${opacity})`;
lineWidth; switch (data.type) {
case 'CLOSED_PLANAR':
draw(context, context => { this._renderClosedPlanar(context, eventData.element, points, {
drawJoinedLines(
context,
eventData.element,
points[points.length - 1],
points,
{
color, color,
lineWidth, lineWidth,
} });
); break;
}); case 'POINT':
this._renderPoint(context, eventData.element, points, {
color,
lineWidth,
});
break;
case 'OPEN_PLANAR':
this._renderOpenPlanar(context, eventData.element, points, {
color,
lineWidth,
});
break;
}
} }
} }
_renderClosedPlanar(context, element, points, options) {
draw(context, context => {
drawJoinedLines(
context,
element,
points[points.length - 1],
points,
options
);
});
}
_renderPoint(context, element, points, options) {
draw(context, context => {
drawCircle(context, element, points[0], 3, options);
});
}
_renderOpenPlanar(context, element, points, options) {
draw(context, context => {
drawJoinedLines(context, element, points[0], points, options);
});
}
} }

View File

@ -5,7 +5,9 @@ import structureSetReferencesSeriesInstanceUid from './lib/structureSetReference
// We should put this as a helper somewhere as we are using it in mutliple places. // We should put this as a helper somewhere as we are using it in mutliple places.
function refreshViewport() { function refreshViewport() {
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element); if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
}); });
} }
@ -81,7 +83,9 @@ function _setStructureSetVisible(SeriesInstanceUID, visible = true) {
const StructureSet = getStructureSet(SeriesInstanceUID); const StructureSet = getStructureSet(SeriesInstanceUID);
if (StructureSet) { if (StructureSet) {
StructureSet.visible = visible; StructureSet.ROIContours.forEach(ROIContour => {
ROIContour.visible = visible;
});
refreshViewport(); refreshViewport();
} }
@ -154,15 +158,10 @@ function setToggleROIContour(SeriesInstanceUID, ROINumber) {
* Returns an array of StructureSets which reference the given SeriesInstanceUID. * Returns an array of StructureSets which reference the given SeriesInstanceUID.
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check. * @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
*/ */
function getStructuresSetsWhichReferenceSeriesInstanceUid( function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
SeriesInstanceUID
) {
const { StructureSets } = state; const { StructureSets } = state;
return StructureSets.filter(StructureSet => return StructureSets.filter(StructureSet =>
structureSetReferencesSeriesInstanceUid( structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
StructureSet,
SeriesInstanceUID
)
); );
} }

View File

@ -3,6 +3,74 @@
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.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.0...@ohif/extension-dicom-segmentation@0.4.1) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.4...@ohif/extension-dicom-segmentation@0.4.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [0.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.3...@ohif/extension-dicom-segmentation@0.3.4) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.2...@ohif/extension-dicom-segmentation@0.3.3) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.1...@ohif/extension-dicom-segmentation@0.3.2) (2020-07-13)
### Bug Fixes
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
## [0.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.0...@ohif/extension-dicom-segmentation@0.3.1) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.6...@ohif/extension-dicom-segmentation@0.3.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.5...@ohif/extension-dicom-segmentation@0.2.6) (2020-05-12) ## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.5...@ohif/extension-dicom-segmentation@0.2.6) (2020-05-12)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.2.6", "version": "0.4.1",
"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",
@ -30,8 +30,8 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "^0.12.2", "dcmjs": "0.16.0",
"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"

View File

@ -8,6 +8,11 @@
flex-direction: column; flex-direction: column;
} }
.dcmseg-segmentation-panel.disabled {
opacity: 0.6;
pointer-events: none;
}
.dcmseg-segmentation-panel h3 { .dcmseg-segmentation-panel h3 {
padding-left: 20px; padding-left: 20px;
margin-top: 0; margin-top: 0;

View File

@ -6,6 +6,9 @@ import moment from 'moment';
import { utils, log } from '@ohif/core'; import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui'; import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import setActiveLabelmap from '../../utils/setActiveLabelMap';
import refreshViewports from '../../utils/refreshViewports';
import { import {
BrushColorSelector, BrushColorSelector,
BrushRadius, BrushRadius,
@ -19,12 +22,6 @@ import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
const { studyMetadataManager } = utils; const { studyMetadataManager } = utils;
const refreshViewport = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
};
/** /**
* SegmentationPanel component * SegmentationPanel component
* *
@ -49,8 +46,8 @@ const SegmentationPanel = ({
onConfigurationChange, onConfigurationChange,
onDisplaySetLoadFailure, onDisplaySetLoadFailure,
onSelectedSegmentationChange, onSelectedSegmentationChange,
activeContexts, activeContexts = [],
contexts, contexts = {},
}) => { }) => {
const isVTK = () => activeContexts.includes(contexts.VTK); const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE); const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@ -68,8 +65,7 @@ const SegmentationPanel = ({
*/ */
const [state, setState] = useState({ const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS, brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: brushColor: 'rgba(221, 85, 85, 1)',
'rgba(221, 85, 85, 1)',
selectedSegment: null, selectedSegment: null,
selectedSegmentation: null, selectedSegmentation: null,
showSegmentationSettings: false, showSegmentationSettings: false,
@ -77,7 +73,8 @@ const SegmentationPanel = ({
labelmapList: [], labelmapList: [],
segmentList: [], segmentList: [],
cachedSegmentsProperties: [], cachedSegmentsProperties: [],
isLoading: false isLoading: false,
isDisabled: true,
}); });
useEffect(() => { useEffect(() => {
@ -111,7 +108,10 @@ const SegmentationPanel = ({
* allows us to easily watch the module or the segmentations loading process in any other component * allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events. * without subscribing to external events.
*/ */
document.addEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations); document.addEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
/* /*
* These are specific to each element; * These are specific to each element;
@ -126,7 +126,10 @@ const SegmentationPanel = ({
); );
return () => { return () => {
document.removeEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations); document.removeEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
cornerstoneTools.store.state.enabledElements.forEach(enabledElement => cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener( enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified', 'cornerstonetoolslabelmapmodified',
@ -139,49 +142,58 @@ const SegmentationPanel = ({
const refreshSegmentations = useCallback(() => { const refreshSegmentations = useCallback(() => {
const module = cornerstoneTools.getModule('segmentation'); const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex]; const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
); if (!isDisabled) {
const firstImageId = studyMetadata.getFirstImageId( const studyMetadata = studyMetadataManager.get(
activeViewport.displaySetInstanceUID activeViewport.StudyInstanceUID
);
const brushStackState = module.state.series[firstImageId];
if (brushStackState) {
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const labelmapList = getLabelmapList(
brushStackState,
firstImageId,
activeViewport
); );
const segmentList = getSegmentList( const firstImageId = studyMetadata.getFirstImageId(
labelmap3D, activeViewport.displaySetInstanceUID
firstImageId,
brushStackState
); );
setState(state => ({ const brushStackState = module.state.series[firstImageId];
...state, if (brushStackState) {
brushStackState, const labelmap3D =
selectedSegmentation: brushStackState.activeLabelmapIndex, brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmapList, const labelmapList = getLabelmapList(
segmentList, brushStackState,
})); firstImageId,
} else { activeViewport
setState(state => ({ );
...state, const segmentList = getSegmentList(
labelmapList: [], labelmap3D,
segmentList: [], firstImageId,
})); brushStackState
);
setState(state => ({
...state,
brushStackState,
selectedSegmentation: brushStackState.activeLabelmapIndex,
labelmapList,
segmentList,
isDisabled,
}));
} else {
setState(state => ({
...state,
labelmapList: [],
segmentList: [],
isDisabled,
}));
}
} }
}, [ }, [viewports, activeIndex, state.isLoading]);
viewports,
activeIndex,
state.isLoading
]);
useEffect(() => { useEffect(() => {
refreshSegmentations(); refreshSegmentations();
}, [viewports, activeIndex, state.selectedSegmentation, activeContexts, state.isLoading]); }, [
viewports,
activeIndex,
isOpen,
state.selectedSegmentation,
activeContexts,
state.isLoading,
]);
/* Handle open/closed panel behaviour */ /* Handle open/closed panel behaviour */
useEffect(() => { useEffect(() => {
@ -216,12 +228,10 @@ const SegmentationPanel = ({
title: displayDescription, title: displayDescription,
description: displayDate, description: displayDate,
onClick: async () => { onClick: async () => {
const activatedLabelmapIndex = await _setActiveLabelmap( const activatedLabelmapIndex = await setActiveLabelmap(
activeViewport, activeViewport,
studies, studies,
displaySet, displaySet,
firstImageId,
brushStackState.activeLabelmapIndex,
() => onSelectedSegmentationChange(), () => onSelectedSegmentationChange(),
onDisplaySetLoadFailure onDisplaySetLoadFailure
); );
@ -369,9 +379,13 @@ const SegmentationPanel = ({
return !segmentsHidden[segmentIndex]; return !segmentsHidden[segmentIndex];
}; };
const cachedSegmentProperties = state.cachedSegmentsProperties[segmentNumber]; const cachedSegmentProperties =
state.cachedSegmentsProperties[segmentNumber];
let visible = isSegmentVisible(); let visible = isSegmentVisible();
if (cachedSegmentProperties && cachedSegmentProperties.visible !== visible) { if (
cachedSegmentProperties &&
cachedSegmentProperties.visible !== visible
) {
toggleSegmentVisibility(); toggleSegmentVisibility();
} }
@ -399,8 +413,10 @@ const SegmentationPanel = ({
onSegmentVisibilityChange(segmentNumber, newVisibility); onSegmentVisibilityChange(segmentNumber, newVisibility);
} }
updateCachedSegmentsProperties(segmentNumber, { visible: newVisibility }); updateCachedSegmentsProperties(segmentNumber, {
refreshViewport(); visible: newVisibility,
});
refreshViewports();
}} }}
/> />
); );
@ -422,10 +438,9 @@ const SegmentationPanel = ({
const segmentsProperties = state.cachedSegmentsProperties; const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber]; const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] = segmentsProperties[segmentNumber] = segmentProperties
segmentProperties ? ? { ...segmentProperties, ...properties }
{ ...segmentProperties, ...properties } : : properties;
properties;
updateState('cachedSegmentsProperties', segmentsProperties); updateState('cachedSegmentsProperties', segmentsProperties);
}; };
@ -492,10 +507,13 @@ const SegmentationPanel = ({
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive; configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive; configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
onConfigurationChange(newConfiguration); onConfigurationChange(newConfiguration);
refreshViewport(); refreshViewports();
}; };
const disabledConfigurationFields = ['outlineAlpha', 'shouldRenderInactiveLabelmaps']; const disabledConfigurationFields = [
'outlineAlpha',
'shouldRenderInactiveLabelmaps',
];
if (state.showSegmentationSettings) { if (state.showSegmentationSettings) {
return ( return (
<SegmentationSettings <SegmentationSettings
@ -507,7 +525,10 @@ const SegmentationPanel = ({
); );
} else { } else {
return ( return (
<div className="dcmseg-segmentation-panel"> <div
className={`dcmseg-segmentation-panel ${state.isDisabled &&
'disabled'}`}
>
<Icon <Icon
className="cog-icon" className="cog-icon"
name="cog" name="cog"
@ -606,57 +627,6 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets; return referencedDisplaysets;
}; };
/**
*
*
* @param {*} viewportSpecificData
* @param {*} studies
* @param {*} displaySet
* @param {*} firstImageId
* @param {*} activeLabelmapIndex
* @returns
*/
const _setActiveLabelmap = async (
viewportSpecificData,
studies,
displaySet,
firstImageId,
activeLabelmapIndex,
callback = () => { },
onDisplaySetLoadFailure
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return displaySet.labelmapIndex;
}
if (!displaySet.isLoaded) {
// What props does this expect `viewportSpecificData` to have?
// TODO: Should this return the `labelmapIndex`?
const loadPromise = displaySet.load(viewportSpecificData, studies);
loadPromise.catch(error => {
onDisplaySetLoadFailure(error);
// Return old index.
return activeLabelmapIndex;
});
await loadPromise;
}
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewport();
callback();
return displaySet.labelmapIndex;
};
/** /**
* *
* @param {*} firstImageId * @param {*} firstImageId
@ -676,7 +646,7 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmap3D.activeSegmentIndex = segmentIndex; labelmap3D.activeSegmentIndex = segmentIndex;
refreshViewport(); refreshViewports();
return segmentIndex; return segmentIndex;
}; };

View File

@ -1,5 +1,6 @@
import { MODULE_TYPES, utils } from '@ohif/core'; import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation'; import loadSegmentation from './loadSegmentation';
import getSourceDisplaySet from './getSourceDisplaySet';
// TODO: Should probably use dcmjs for this // TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = { const SOP_CLASS_UIDS = {
@ -27,6 +28,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
const { const {
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber,
SeriesDescription, SeriesDescription,
FrameOfReferenceUID, FrameOfReferenceUID,
SOPInstanceUID, SOPInstanceUID,
@ -51,9 +53,14 @@ export default function getSopClassHandlerModule({ servicesManager }) {
isLoaded: false, isLoaded: false,
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber,
SeriesDescription, SeriesDescription,
}; };
segDisplaySet.getSourceDisplaySet = function(studies) {
return getSourceDisplaySet(studies, segDisplaySet);
};
segDisplaySet.load = function(referencedDisplaySet, studies) { segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies); return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
}; };

View File

@ -0,0 +1,37 @@
import setActiveLabelmap from './utils/setActiveLabelMap';
export default function getSourceDisplaySet(studies, segDisplaySet) {
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
setActiveLabelmap(referencedDisplaySet, studies, segDisplaySet);
return referencedDisplaySet;
}
const _getReferencedDisplaySet = (segDisplaySet, studies) => {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
);
const ReferencedSeriesSequence = Array.isArray(
segDisplaySet.metadata.ReferencedSeriesSequence
)
? segDisplaySet.metadata.ReferencedSeriesSequence
: [segDisplaySet.metadata.ReferencedSeriesSequence];
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
return referencedDisplaySet;
};

View File

@ -24,7 +24,7 @@ export default {
return toolbarModule; return toolbarModule;
}, },
getPanelModule({ commandsManager, api, servicesManager }) { getPanelModule({ commandsManager, api, servicesManager }) {
const { UINotificationService } = servicesManager; const { UINotificationService } = servicesManager.services;
const ExtendedSegmentationPanel = props => { const ExtendedSegmentationPanel = props => {
const { activeContexts } = api.hooks.useAppContext(); const { activeContexts } = api.hooks.useAppContext();
@ -46,7 +46,7 @@ export default {
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => { const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
commandsManager.runCommand('setSegmentConfiguration', { commandsManager.runCommand('setSegmentConfiguration', {
segmentNumber, segmentNumber,
visible visible,
}); });
}; };
@ -55,7 +55,7 @@ export default {
globalOpacity: configuration.fillAlpha, globalOpacity: configuration.fillAlpha,
outlineThickness: configuration.outlineWidth, outlineThickness: configuration.outlineWidth,
renderOutline: configuration.renderOutline, renderOutline: configuration.renderOutline,
visible: configuration.renderFill visible: configuration.renderFill,
}); });
}; };

View File

@ -41,6 +41,7 @@ export default async function loadSegmentation(
results = _parseSeg(segArrayBuffer, imageIds); results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) { } catch (error) {
segDisplaySet.isLoaded = false; segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error); reject(error);
} }

View File

@ -0,0 +1,5 @@
export default function refreshViewports() {
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
}

View File

@ -0,0 +1,67 @@
import { utils, log } from '@ohif/core';
import cornerstoneTools from 'cornerstone-tools';
import refreshViewports from './refreshViewports';
const { studyMetadataManager } = utils;
/**
*
*
* @param {*} viewportSpecificData
* @param {*} studies
* @param {*} displaySet
* @param {*} firstImageId
* @param {*} activeLabelmapIndex
* @returns
*/
export default async function setActiveLabelmap(
referencedDisplaySet,
studies,
displaySet,
callback = () => {},
onDisplaySetLoadFailure = err => {
throw new Error(err.message);
}
) {
const studyMetadata = studyMetadataManager.get(
referencedDisplaySet.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
referencedDisplaySet.displaySetInstanceUID
);
let { state } = cornerstoneTools.getModule('segmentation');
let brushStackState = state.series[firstImageId];
const activeLabelmapIndex = brushStackState
? brushStackState.activeLabelmapIndex
: undefined;
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return displaySet.labelmapIndex;
}
if (!displaySet.isLoaded) {
const loadPromise = displaySet.load(referencedDisplaySet, studies);
loadPromise.catch(error => {
onDisplaySetLoadFailure(error);
// Return old index.
return activeLabelmapIndex;
});
await loadPromise;
}
// This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewports();
callback();
return displaySet.labelmapIndex;
}

View File

@ -3,6 +3,130 @@
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.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.1...@ohif/extension-vtk@1.7.2) (2020-08-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.0...@ohif/extension-vtk@1.7.1) (2020-08-24)
### Bug Fixes
* 🐛 Fail gracefully on an MPR load error ([#1992](https://github.com/OHIF/Viewers/issues/1992)) ([779a7e0](https://github.com/OHIF/Viewers/commit/779a7e0976aa3a2d2bf1dc8f056950f0545c9fff))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.10...@ohif/extension-vtk@1.7.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [1.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.9...@ohif/extension-vtk@1.6.10) (2020-08-10)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.8...@ohif/extension-vtk@1.6.9) (2020-07-23)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.7...@ohif/extension-vtk@1.6.8) (2020-07-22)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.6...@ohif/extension-vtk@1.6.7) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.5...@ohif/extension-vtk@1.6.6) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.4...@ohif/extension-vtk@1.6.5) (2020-07-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.3...@ohif/extension-vtk@1.6.4) (2020-06-18)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.2...@ohif/extension-vtk@1.6.3) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.1...@ohif/extension-vtk@1.6.2) (2020-06-05)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.0...@ohif/extension-vtk@1.6.1) (2020-06-04)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.6...@ohif/extension-vtk@1.6.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.5...@ohif/extension-vtk@1.5.6) (2020-05-14) ## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.5...@ohif/extension-vtk@1.5.6) (2020-05-14)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.5.6", "version": "1.7.2",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@ -33,9 +33,9 @@
"@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.2.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "0.16.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
@ -50,12 +50,12 @@
"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.9.0" "react-vtkjs-viewport": "^0.10.4"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.9.6", "@ohif/core": "^2.10.1",
"@ohif/ui": "^1.4.4", "@ohif/ui": "^1.5.3",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1", "gh-pages": "^2.0.1",

View File

@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
</div> </div>
</div> </div>
) : ( ) : (
<div className="imageViewerLoadingIndicator loadingIndicator"> <div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents"> <div className="indicatorContents">
<p> <p>
{this.props.t('Loading...')} {this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" /> <i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete} {percComplete}
</p> </p>
</div>
</div> </div>
)} </div>
)}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -49,27 +49,30 @@ class OHIFVTKViewport extends Component {
state = { state = {
volumes: null, volumes: null,
paintFilterLabelMapImageData: null, paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null paintFilterBackgroundImageData: null,
percentComplete: 0,
isLoaded: false,
}; };
static propTypes = { static propTypes = {
viewportData: PropTypes.shape({ viewportData: PropTypes.shape({
studies: PropTypes.array, studies: PropTypes.array.isRequired,
displaySet: PropTypes.shape({ displaySet: PropTypes.shape({
StudyInstanceUID: PropTypes.string, StudyInstanceUID: PropTypes.string.isRequired,
displaySetInstanceUID: PropTypes.string, displaySetInstanceUID: PropTypes.string.isRequired,
sopClassUIDs: PropTypes.arrayOf(PropTypes.string), sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
SOPInstanceUID: PropTypes.string, SOPInstanceUID: PropTypes.string,
frameIndex: PropTypes.number, frameIndex: PropTypes.number,
}), }),
}), }),
viewportIndex: PropTypes.number, viewportIndex: PropTypes.number.isRequired,
children: PropTypes.node, children: PropTypes.node,
onScroll: PropTypes.func, onScroll: PropTypes.func,
servicesManager: PropTypes.object.isRequired,
}; };
static defaultProps = { static defaultProps = {
onScroll: () => { }, onScroll: () => {},
}; };
static id = 'OHIFVTKViewport'; static id = 'OHIFVTKViewport';
@ -156,9 +159,11 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState; const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => { this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
return { visible: !isHidden }; isHidden => {
}); return { visible: !isHidden };
}
);
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`; const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
@ -349,7 +354,7 @@ class OHIFVTKViewport 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
) { ) {
@ -360,34 +365,54 @@ class OHIFVTKViewport extends Component {
loadProgressively(imageDataObject) { loadProgressively(imageDataObject) {
loadImageData(imageDataObject); loadImageData(imageDataObject);
const { isLoading, insertPixelDataPromises } = imageDataObject; const { isLoading, imageIds } = imageDataObject;
const NumberOfFrames = insertPixelDataPromises.length;
if (!isLoading) { if (!isLoading) {
this.setState({ isLoaded: true }); this.setState({ isLoaded: true });
return; return;
} }
insertPixelDataPromises.forEach(promise => { const NumberOfFrames = imageIds.length;
promise.then(numberProcessed => {
const percentComplete = Math.floor(
(numberProcessed * 100) / NumberOfFrames
);
if (percentComplete !== this.state.percentComplete) { const onPixelDataInsertedCallback = numberProcessed => {
this.setState({ const percentComplete = Math.floor(
percentComplete, (numberProcessed * 100) / NumberOfFrames
);
if (percentComplete !== this.state.percentComplete) {
this.setState({
percentComplete,
});
}
};
const onPixelDataInsertedErrorCallback = error => {
const { UINotificationService } = this.props.servicesManager.services;
if (!this.hasError) {
if (this.props.viewportIndex === 0) {
// Only show the notification from one viewport 1 in MPR2D.
UINotificationService.show({
title: 'MPR Load Error',
message: error.message,
type: 'error',
autoClose: false,
}); });
} }
});
});
Promise.all(insertPixelDataPromises).then(() => { this.hasError = true;
}
};
const onAllPixelDataInsertedCallback = () => {
this.setState({ this.setState({
isLoaded: true, isLoaded: true,
}); });
}); };
imageDataObject.onPixelDataInserted(onPixelDataInsertedCallback);
imageDataObject.onAllPixelDataInserted(onAllPixelDataInsertedCallback);
imageDataObject.onPixelDataInsertedError(onPixelDataInsertedErrorCallback);
} }
render() { render() {
@ -435,7 +460,7 @@ class OHIFVTKViewport extends Component {
segmentsDefaultProperties: this.segmentsDefaultProperties, segmentsDefaultProperties: this.segmentsDefaultProperties,
onNewSegmentationRequested: () => { onNewSegmentationRequested: () => {
this.setStateFromProps(); this.setStateFromProps();
} },
}} }}
onScroll={this.props.onScroll} onScroll={this.props.onScroll}
/> />

View File

@ -1,3 +1,4 @@
import React from 'react';
import asyncComponent from './asyncComponent.js'; import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js'; import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js'; import toolbarModule from './toolbarModule.js';
@ -15,8 +16,11 @@ const vtkExtension = {
*/ */
id: 'vtk', id: 'vtk',
getViewportModule({ commandsManager }) { getViewportModule({ commandsManager, servicesManager }) {
return withCommandsManager(OHIFVTKViewport, commandsManager); const ExtendedVTKViewport = props => (
<OHIFVTKViewport {...props} servicesManager={servicesManager} />
);
return withCommandsManager(ExtendedVTKViewport, commandsManager);
}, },
getToolbarModule() { getToolbarModule() {
return toolbarModule; return toolbarModule;

26
netlify-lerna-cache.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/sh
NODE_MODULES_CACHE="./node_modules"
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
echo "Running netlify-lerna-cache.sh"
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
cache_deps() {
PACKAGES=$(ls -1 $1)
for PKG in $PACKAGES
do
PKG_NODE_MODULES="$1/$PKG/node_modules"
if [ -d $PKG_NODE_MODULES ];
then
mv $PKG_NODE_MODULES $LERNA_CACHE/$PKG
echo "Cached node modules for $PKG"
else
echo "Unable to cache node modules for $PKG"
fi
done
}
cache_deps platform
cache_deps extensions
cache_deps modes

27
netlify-lerna-restore.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/sh
NODE_MODULES_CACHE="./node_modules"
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
echo "Running netlify-lerna-restore.sh"
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
echo "$NODE_MODULES_CACHE/lerna-cache/*"
restore_deps() {
PACKAGES=$(ls -1 $1)
for PKG in $PACKAGES
do
PKG_CACHE="$LERNA_CACHE/$PKG"
if [ -d $PKG_CACHE ];
then
mv $PKG_CACHE $1/$PKG/node_modules
echo "Restored node modules for $PKG"
else
echo "Unable to restore cache for $PKG"
fi
done
}
restore_deps platform
restore_deps extensions
restore_deps modes

View File

@ -8,6 +8,11 @@
# managed by CircleCI and deployed to our Google Hosting # managed by CircleCI and deployed to our Google Hosting
# #
[build]
base = ""
publish = ".netlify/www/"
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
# NODE_VERSION in root `.nvmrc` takes priority # NODE_VERSION in root `.nvmrc` takes priority
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn # YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
[build.environment] [build.environment]
@ -16,19 +21,13 @@
YARN_VERSION = "1.17.3" YARN_VERSION = "1.17.3"
RUBY_VERSION = "2.6.2" RUBY_VERSION = "2.6.2"
YARN_FLAGS = "--no-ignore-optional --pure-lockfile" YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
NETLIFY_RESTORE = "1"
# Production context: all deploys from the Production branch set in your site's # Production context: all deploys from the Production branch set in your site's
# deploy contexts will inherit these settings. # deploy contexts will inherit these settings.
[context.production] [context.production]
ignore = "exit 0" # Never build production; We'll let our CI do that ignore = "exit 0" # Never build production; We'll let our CI do that
# Deploy Preview context: all deploys generated from a pull/merge request will
# inherit these settings.
[context.deploy-preview]
base = ""
publish = ".netlify/www/"
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
[[headers]] [[headers]]
# Define which paths this specific [[headers]] block will cover. # Define which paths this specific [[headers]] block will cover.
for = "/*" for = "/*"

View File

@ -34,6 +34,8 @@
"see-changed": "lerna changed", "see-changed": "lerna changed",
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh", "docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
"release": "yarn run lerna:version && yarn run lerna:publish", "release": "yarn run lerna:version && yarn run lerna:publish",
"lerna:cache": "./netlify-lerna-cache.sh",
"lerna:restore": "./netlify-lerna-restore.sh",
"lerna:version": "npx lerna version prerelease --force-publish", "lerna:version": "npx lerna version prerelease --force-publish",
"lerna:publish": "lerna publish from-package --canary --dist-tag canary", "lerna:publish": "lerna publish from-package --canary --dist-tag canary",
"link-list": "npm ls --depth=0 --link=true" "link-list": "npm ls --depth=0 --link=true"

View File

@ -3,6 +3,101 @@
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.10.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.0...@ohif/core@2.10.1) (2020-08-28)
**Note:** Version bump only for package @ohif/core
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.14...@ohif/core@2.10.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [2.9.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.13...@ohif/core@2.9.14) (2020-08-10)
**Note:** Version bump only for package @ohif/core
## [2.9.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.12...@ohif/core@2.9.13) (2020-07-22)
### Bug Fixes
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
## [2.9.12](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.11...@ohif/core@2.9.12) (2020-07-13)
### Bug Fixes
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
## [2.9.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.10...@ohif/core@2.9.11) (2020-07-13)
**Note:** Version bump only for package @ohif/core
## [2.9.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.9...@ohif/core@2.9.10) (2020-07-13)
### Bug Fixes
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
## [2.9.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.8...@ohif/core@2.9.9) (2020-06-18)
**Note:** Version bump only for package @ohif/core
## [2.9.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.7...@ohif/core@2.9.8) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
## [2.9.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.6...@ohif/core@2.9.7) (2020-06-04)
**Note:** Version bump only for package @ohif/core
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14) ## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.9.6", "version": "2.10.1",
"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",
@ -32,14 +32,14 @@
}, },
"peerDependencies": { "peerDependencies": {
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.3"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"dcmjs": "^0.12.2", "dcmjs": "0.16.0",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.6.0",
"immer": "6.0.2", "immer": "6.0.2",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",

View File

@ -13,7 +13,7 @@ const findMostRecentStructuredReport = studies => {
// Skip series that may not have instances yet // Skip series that may not have instances yet
// This can happen if we have retrieved just the initial // This can happen if we have retrieved just the initial
// details about the series via QIDO-RS, but not the full metadata // details about the series via QIDO-RS, but not the full metadata
if (!series.instances.length) { if (!series.instances || !series.instances.length) {
return; return;
} }

View File

@ -54,9 +54,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
// Get Study display sets // Get Study display sets
const displaySets = studyMetadata.createDisplaySets(); const displaySets = studyMetadata.createDisplaySets();
// Set studyMetadata display sets
studyMetadata.setDisplaySets(displaySets);
OHIFStudyMetadataSource._updateStudyCollections(studyMetadata); OHIFStudyMetadataSource._updateStudyCollections(studyMetadata);
resolve(studyMetadata); resolve(studyMetadata);
}) })

View File

@ -10,8 +10,8 @@ import { api } from 'dicomweb-client';
// - createStacks // - createStacks
import { isImage } from '../../utils/isImage'; import { isImage } from '../../utils/isImage';
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable'; import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
import errorHandler from '../../errorHandler'; import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
export class StudyMetadata extends Metadata { export class StudyMetadata extends Metadata {
constructor(data, uid) { constructor(data, uid) {
@ -79,7 +79,7 @@ export class StudyMetadata extends Metadata {
Object.defineProperty(this, 'studyInstanceUID', { Object.defineProperty(this, 'studyInstanceUID', {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
get: function () { get: function() {
return this.getStudyInstanceUID(); return this.getStudyInstanceUID();
}, },
}); });
@ -135,12 +135,15 @@ export class StudyMetadata extends Metadata {
study, study,
sopClassUIDs sopClassUIDs
); );
if (displaySet) { if (displaySet) {
displaySet.sopClassModule = true; displaySet.sopClassModule = true;
displaySet.isDerived if (displaySet.isDerived) {
? this._addDerivedDisplaySet(displaySet) this._addDerivedDisplaySet(displaySet);
: displaySets.push(displaySet); }
displaySets.push(displaySet);
return displaySets; return displaySets;
} }
@ -215,6 +218,14 @@ export class StudyMetadata extends Metadata {
// TODO -> Get x Modality by referencedSeriesInstanceUid, FoR, etc. // TODO -> Get x Modality by referencedSeriesInstanceUid, FoR, etc.
} }
/**
* Adds the displaySets to the studies list of derived displaySets.
* @param {array} displaySets The displaySets array to append to the derived displaysets list.
*/
_addDerivedDisplaySets(displaySets) {
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
}
/** /**
* 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.
* @param {object} filter An object containing search filters * @param {object} filter An object containing search filters
@ -263,7 +274,7 @@ export class StudyMetadata extends Metadata {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter( filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet =>
displaySet.ReferencedFrameOfReferenceUID === displaySet.ReferencedFrameOfReferenceUID ===
ReferencedFrameOfReferenceUID referencedFrameOfReferenceUID
); );
} }
@ -297,14 +308,10 @@ export class StudyMetadata extends Metadata {
series series
); );
displaySets.push(...displaySetsForSeries); displaySetsForSeries.forEach(ds => this._insertDisplaySet(ds));
}); });
return sortDisplaySetList(displaySets); return this._displaySets;
}
sortDisplaySets() {
sortDisplaySetList(this._displaySets);
} }
/** /**
@ -335,25 +342,9 @@ export class StudyMetadata extends Metadata {
this.addDisplaySet(displaySet); this.addDisplaySet(displaySet);
}); });
this.sortDisplaySets();
return true; return true;
} }
/**
* Set display sets
* @param {Array} displaySets Array of display sets (ImageSet[])
*/
setDisplaySets(displaySets) {
if (Array.isArray(displaySets) && displaySets.length > 0) {
// TODO: This is weird, can we just switch it to writable: true?
this._displaySets.splice(0);
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
this.sortDisplaySets();
}
}
/** /**
* Add a single display set to the list * Add a single display set to the list
* @param {Object} displaySet Display set object * @param {Object} displaySet Display set object
@ -361,7 +352,7 @@ export class StudyMetadata extends Metadata {
*/ */
addDisplaySet(displaySet) { addDisplaySet(displaySet) {
if (displaySet instanceof ImageSet || displaySet.sopClassModule) { if (displaySet instanceof ImageSet || displaySet.sopClassModule) {
this._displaySets.push(displaySet); this._insertDisplaySet(displaySet);
return true; return true;
} }
return false; return false;
@ -382,6 +373,96 @@ export class StudyMetadata extends Metadata {
} }
} }
/**
* Insert the displaySet so that the list has an increasing SeriesNumber,
* with the most recent series first for displaySets with the same SeriesNumber.
*
* If the displaySet is low priority, the same logic is applied, but is sorted within a sub list
* At the end of the list, where all low priority data is found.
*/
_insertDisplaySet(displaySet) {
const { SeriesNumber } = displaySet;
const displaySets = this._displaySets;
let insertIndex = displaySets.length;
let firstIndexWithSameSeriesNumber;
// If low priority, start search from next low priority.
if (isLowPriorityModality(displaySet.Modality)) {
let startingIndex;
// Find where the first low priority displaySet is.
for (let i = 0; i < displaySets.length; i++) {
if (isLowPriorityModality(displaySets[i].Modality)) {
startingIndex = i;
break;
}
}
if (!startingIndex) {
startingIndex = displaySets.length;
}
// Find the correct SeriesNumber location to insert within the low priority
// Modality displaySets
for (let i = startingIndex; i < displaySets.length; i++) {
if (
displaySets[i].SeriesNumber === SeriesNumber &&
!firstIndexWithSameSeriesNumber
) {
firstIndexWithSameSeriesNumber = i;
}
if (displaySets[i].SeriesNumber > SeriesNumber) {
insertIndex = i;
break;
}
}
} else {
// Find correct SeriesNumber to insert or where the low priority modalities start.
for (let i = 0; i < displaySets.length; i++) {
if (
displaySets[i].SeriesNumber === SeriesNumber &&
!firstIndexWithSameSeriesNumber
) {
firstIndexWithSameSeriesNumber = i;
}
if (
displaySets[i].SeriesNumber > SeriesNumber ||
isLowPriorityModality(displaySets[i].Modality)
) {
insertIndex = i;
break;
}
}
}
// If we have multiple displaySets with the same series number, find the insert position based on
// SeriesDate and SeriesTime.
if (firstIndexWithSameSeriesNumber !== undefined) {
// If no SeriesDate, is just a placeholder displaySet, just insert anywhere, it will be re-added later.
if (displaySet.SeriesDate) {
const seriesDateTime = `${displaySet.SeriesDate}${displaySet.SeriesTime}`;
for (let i = firstIndexWithSameSeriesNumber; i < insertIndex; i++) {
const displaySetI = displaySets[i];
if (
displaySetI.SeriesDate &&
`${displaySetI.SeriesDate}${displaySetI.SeriesTime}` <
seriesDateTime
) {
insertIndex = i;
break;
}
}
}
}
this._displaySets.splice(insertIndex, 0, displaySet);
this.displaySets = this._displaySets;
}
/** /**
* Search the associated display sets using the supplied callback as criteria. The callback is passed * Search the associated display sets using the supplied callback as criteria. The callback is passed
* two arguments: display set (an ImageSet instance) and index (the integer * two arguments: display set (an ImageSet instance) and index (the integer
@ -537,49 +618,6 @@ export class StudyMetadata extends Metadata {
return this._series.indexOf(series); return this._series.indexOf(series);
} }
/**
* It sorts the series based on display sets order. Each series must be an instance
* of SeriesMetadata and each display sets must be an instance of ImageSet.
* Useful example of usage:
* Study data provided by backend does not sort series at all and client-side
* needs series sorted by the same criteria used for sorting display sets.
*/
sortSeriesByDisplaySets() {
// Object for mapping display sets' index by SeriesInstanceUID
const displaySetsMapping = {};
// Loop through each display set to create the mapping
this.forEachDisplaySet((displaySet, index) => {
if (!(displaySet instanceof ImageSet)) {
throw new OHIFError(
`StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet`
);
}
// In case of multiframe studies, just get the first index occurence
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
}
});
// Clone of actual series
const actualSeries = this.getSeries();
actualSeries.forEach((series, index) => {
if (!(series instanceof SeriesMetadata)) {
throw new OHIFError(
`StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata`
);
}
// Get the new series index
const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()];
// Update the series object with the new series position
this._series[seriesIndex] = series;
});
}
/** /**
* Compares the current study instance with another one. * Compares the current study instance with another one.
* @param {StudyMetadata} study An instance of the StudyMetadata class. * @param {StudyMetadata} study An instance of the StudyMetadata class.
@ -854,53 +892,3 @@ function _getDisplaySetFromSopClassModule(
} }
return displaySet; return displaySet;
} }
/**
* Sort series primarily by Modality (i.e., series with references to other
* series like SEG, KO or PR are grouped in the end of the list) and then by
* series number:
*
* --------
* | CT #3 |
* | CT #4 |
* | CT #5 |
* --------
* | SEG #1 |
* | SEG #2 |
* --------
*
* @param {*} a - DisplaySet
* @param {*} b - DisplaySet
*/
function seriesSortingCriteria(a, b) {
const isLowPriorityA = isLowPriorityModality(a.Modality);
const isLowPriorityB = isLowPriorityModality(b.Modality);
if (!isLowPriorityA && isLowPriorityB) {
return -1;
}
if (isLowPriorityA && !isLowPriorityB) {
return 1;
}
return sortBySeriesNumber(a, b);
}
/**
* Sort series by series number. Series with low
* @param {*} a - DisplaySet
* @param {*} b - DisplaySet
*/
function sortBySeriesNumber(a, b) {
const seriesNumberAIsGreaterOrUndefined =
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
}
/**
* Sorts a list of display set objects
* @param {Array} list A list of display sets to be sorted
*/
function sortDisplaySetList(list) {
return list.sort(seriesSortingCriteria);
}

View File

@ -251,7 +251,9 @@ export default class MeasurementApi {
this.syncMeasurementsAndToolData(); this.syncMeasurementsAndToolData();
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element); if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
}); });
// Let others know that the measurements are updated // Let others know that the measurements are updated

View File

@ -0,0 +1,9 @@
import cornerstone from 'cornerstone-core';
export default function refreshCornerstoneViewports() {
cornerstone.getEnabledElements().forEach(enabledElement => {
if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
});
}

View File

@ -4,6 +4,7 @@ import log from '../../log';
import user from '../../user'; import user from '../../user';
import getImageAttributes from '../lib/getImageAttributes'; import getImageAttributes from '../lib/getImageAttributes';
import getLabel from '../lib/getLabel'; import getLabel from '../lib/getLabel';
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
export default function({ eventData, tool, toolGroupId, toolGroup }) { export default function({ eventData, tool, toolGroupId, toolGroup }) {
const measurementApi = MeasurementApi.Instance; const measurementApi = MeasurementApi.Instance;
@ -86,9 +87,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
} }
// TODO: This is very hacky, but will work for now // TODO: This is very hacky, but will work for now
cornerstone.getEnabledElements().forEach(enabledElement => { refreshCornerstoneViewports();
cornerstone.updateImage(enabledElement.element);
});
// TODO: Notify about the last activated measurement // TODO: Notify about the last activated measurement

View File

@ -1,6 +1,7 @@
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import { MeasurementApi } from '../classes'; import { MeasurementApi } from '../classes';
import log from '../../log'; import log from '../../log';
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
export default function({ eventData, tool, toolGroupId, toolGroup }) { export default function({ eventData, tool, toolGroupId, toolGroup }) {
log.info('CornerstoneToolsMeasurementRemoved'); log.info('CornerstoneToolsMeasurementRemoved');
@ -37,9 +38,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
} }
// TODO: This is very hacky, but will work for now // TODO: This is very hacky, but will work for now
cornerstone.getEnabledElements().forEach(enabledElement => { refreshCornerstoneViewports();
cornerstone.updateImage(enabledElement.element);
});
if (MeasurementApi.isToolIncluded(tool)) { if (MeasurementApi.isToolIncluded(tool)) {
// TODO: Notify that viewer suffered changes // TODO: Notify that viewer suffered changes

View File

@ -4,6 +4,7 @@ import log from '../../log';
import user from '../../user'; import user from '../../user';
import getImageAttributes from '../lib/getImageAttributes'; import getImageAttributes from '../lib/getImageAttributes';
import getLabel from '../lib/getLabel'; import getLabel from '../lib/getLabel';
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
export default function handleSingleMeasurementAdded({ eventData, tool }) { export default function handleSingleMeasurementAdded({ eventData, tool }) {
const measurementApi = MeasurementApi.Instance; const measurementApi = MeasurementApi.Instance;
@ -39,9 +40,7 @@ export default function handleSingleMeasurementAdded({ eventData, tool }) {
} }
// TODO: This is very hacky, but will work for now // TODO: This is very hacky, but will work for now
cornerstone.getEnabledElements().forEach(enabledElement => { refreshCornerstoneViewports();
cornerstone.updateImage(enabledElement.element);
});
// TODO: Notify about the last activated measurement // TODO: Notify about the last activated measurement

View File

@ -1,6 +1,7 @@
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import { MeasurementApi } from '../classes'; import { MeasurementApi } from '../classes';
import log from '../../log'; import log from '../../log';
import refreshCornerstoneViewports from '../lib/refreshCornerstoneViewports';
export default function handleSingleMeasurementRemoved({ export default function handleSingleMeasurementRemoved({
eventData, eventData,
@ -35,9 +36,7 @@ export default function handleSingleMeasurementRemoved({
}); });
// TODO: This is very hacky, but will work for now // TODO: This is very hacky, but will work for now
cornerstone.getEnabledElements().forEach(enabledElement => { refreshCornerstoneViewports();
cornerstone.updateImage(enabledElement.element);
});
if (MeasurementApi.isToolIncluded(tool)) { if (MeasurementApi.isToolIncluded(tool)) {
// TODO: Notify that viewer suffered changes // TODO: Notify that viewer suffered changes

View File

@ -6,11 +6,12 @@ const defaultState = {
const servers = (state = defaultState, action) => { const servers = (state = defaultState, action) => {
switch (action.type) { switch (action.type) {
case 'SET_STUDY_DATA': case 'SET_STUDY_DATA': {
const updatedStudyData = cloneDeep(state).studyData; const updatedStudyData = cloneDeep(state.studyData);
updatedStudyData[action.StudyInstanceUID] = action.data; updatedStudyData[action.StudyInstanceUID] = cloneDeep(action.data);
return Object.assign({}, state, { studyData: updatedStudyData }); return Object.assign({}, state, { studyData: updatedStudyData });
}
default: default:
return state; return state;
} }

View File

@ -28,13 +28,14 @@ const serviceImplementation = {
/** /**
* Show a new UI modal; * Show a new UI modal;
* *
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, closeButton, title, customClassName } * @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName }
*/ */
function _show({ function _show({
content = null, content = null,
contentProps = null, contentProps = null,
shouldCloseOnEsc = false, shouldCloseOnEsc = false,
isOpen = true, isOpen = true,
onClose = null,
closeButton = true, closeButton = true,
title = null, title = null,
customClassName = null, customClassName = null,
@ -44,6 +45,7 @@ function _show({
contentProps, contentProps,
shouldCloseOnEsc, shouldCloseOnEsc,
isOpen, isOpen,
onClose,
closeButton, closeButton,
title, title,
customClassName, customClassName,

View File

@ -1,4 +1,11 @@
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']); const LOW_PRIORITY_MODALITIES = Object.freeze([
'SEG',
'DOC',
'RTSTRUCT',
'SR',
'KO',
'PR',
]);
export default function isLowPriorityModality(Modality) { export default function isLowPriorityModality(Modality) {
return LOW_PRIORITY_MODALITIES.includes(Modality); return LOW_PRIORITY_MODALITIES.includes(Modality);

View File

@ -82,12 +82,17 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
// For each type, see if any are loaded, if not load the most recent. // For each type, see if any are loaded, if not load the most recent.
Object.keys(displaySetsPerModality).forEach(key => { Object.keys(displaySetsPerModality).forEach(key => {
const displaySets = displaySetsPerModality[key]; const displaySets = displaySetsPerModality[key];
const isLoaded = displaySets.some(displaySet => displaySet.isLoaded); const isLoaded = displaySets.some(displaySet => displaySet.isLoaded);
if (isLoaded) { if (isLoaded) {
return; return;
} }
if (displaySets.some(displaySet => displaySet.loadError)) {
return;
}
// find most recent and load it. // find most recent and load it.
let recentDateTime = 0; let recentDateTime = 0;
let recentDisplaySet = displaySets[0]; let recentDisplaySet = displaySets[0];
@ -102,6 +107,8 @@ const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
} }
}); });
recentDisplaySet.isLoading = true;
promises.push(recentDisplaySet.load(referencedDisplaySet, studies)); promises.push(recentDisplaySet.load(referencedDisplaySet, studies));
}); });

View File

@ -3,6 +3,44 @@
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.5.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.2...@ohif/ui@1.5.3) (2020-07-23)
**Note:** Version bump only for package @ohif/ui
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.1...@ohif/ui@1.5.2) (2020-07-22)
### Bug Fixes
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.0...@ohif/ui@1.5.1) (2020-06-05)
**Note:** Version bump only for package @ohif/ui
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.4...@ohif/ui@1.5.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
## [1.4.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.3...@ohif/ui@1.4.4) (2020-05-04) ## [1.4.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.3...@ohif/ui@1.4.4) (2020-05-04)

View File

@ -0,0 +1,25 @@
const path = require('path');
// ~~ Plugins
const PnpWebpackPlugin = require(`pnp-webpack-plugin`); // Required until Webpack@5
exports.onCreateWebpackConfig = args => {
args.actions.setWebpackConfig({
node: { fs: 'empty' },
resolve: {
plugins: [PnpWebpackPlugin],
// Note the '..' in the path because docz gatsby project lives in the '.docz' directory
modules: [
// platform/ui
path.resolve(__dirname, '../node_modules'),
// .docz
'node_modules',
],
// resolve: {
// symlinks: true,
// },
},
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
});
};

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.4.4", "version": "1.5.3",
"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",
@ -41,20 +41,24 @@
"@ohif/i18n": "^0.2.3", "@ohif/i18n": "^0.2.3",
"browser-detect": "^0.2.28", "browser-detect": "^0.2.28",
"classnames": "2.2.6", "classnames": "2.2.6",
"dom-helpers": "^3.3.1",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
"moment": "2.24.0", "moment": "2.24.0",
"prop-types": "15.6.2", "prop-types": "15.6.2",
"react-bootstrap-modal": "4.2.0", "prop-types-extra": "^1.1.1",
"react-dates": "21.2.1", "react-dates": "21.2.1",
"react-dnd": "9.4.0", "react-dnd": "9.4.0",
"react-dnd-html5-backend": "^9.4.0", "react-dnd-html5-backend": "^9.4.0",
"react-dnd-touch-backend": "^9.4.0", "react-dnd-touch-backend": "^9.4.0",
"react-draggable": "^4.1.0", "react-draggable": "^4.1.0",
"react-error-boundary": "^2.2.1",
"react-i18next": "^10.11.0", "react-i18next": "^10.11.0",
"react-modal": "^3.11.1", "react-modal": "^3.11.1",
"react-overlays": "^0.8.0",
"react-transition-group": "2.0.0",
"react-with-direction": "1.3.0" "react-with-direction": "1.3.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary } from 'react-error-boundary';
import './ErrorFallback.css';
const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
return (
<div className="ErrorFallback" role="alert">
<p>Something went wrong.</p>
<pre>{error.message}</pre>
<pre>{componentStack}</pre>
</div>
);
};
const OHIFErrorBoundary = ({
context = 'OHIF',
onReset = () => {},
onError = () => {},
fallbackComponent,
children,
}) => {
const onErrorHandler = (error, componentStack) => {
console.error(`${context} Error Boundary`, error, componentStack);
onError(error, componentStack);
};
const onResetHandler = () => {
onReset();
};
return (
<ErrorBoundary
FallbackComponent={fallbackComponent || ErrorFallback}
onReset={onResetHandler}
onError={onErrorHandler}
>
{children}
</ErrorBoundary>
);
};
OHIFErrorBoundary.propTypes = {
context: PropTypes.string,
onReset: PropTypes.func,
onError: PropTypes.func,
children: PropTypes.node.isRequired,
fallbackComponent: PropTypes.element,
};
export default OHIFErrorBoundary;

View File

@ -0,0 +1,4 @@
.ErrorFallback {
padding: 10px;
color: var(--active-color);
}

View File

@ -0,0 +1 @@
export { default as ErrorBoundary } from './ErrorBoundary';

View File

@ -7,7 +7,6 @@ import { AboutContent } from './content/aboutContent/AboutContent';
import { TabComponents, TabFooter } from './tabComponents'; import { TabComponents, TabFooter } from './tabComponents';
import { HotkeyField } from './customForm'; import { HotkeyField } from './customForm';
import { LanguageSwitcher } from './languageSwitcher'; import { LanguageSwitcher } from './languageSwitcher';
import { Checkbox } from './checkbox'; import { Checkbox } from './checkbox';
import { CineDialog } from './cineDialog'; import { CineDialog } from './cineDialog';
import { ViewportDownloadForm } from './content/viewportDownloadForm'; import { ViewportDownloadForm } from './content/viewportDownloadForm';
@ -25,8 +24,10 @@ import {
} from './studyList'; } from './studyList';
import { ToolbarSection } from './toolbarSection'; import { ToolbarSection } from './toolbarSection';
import { Tooltip } from './tooltip'; import { Tooltip } from './tooltip';
import { ErrorBoundary } from './errorBoundary';
export { export {
ErrorBoundary,
ContextMenu, ContextMenu,
Checkbox, Checkbox,
CineDialog, CineDialog,

View File

@ -32,6 +32,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
contentProps: null, contentProps: null,
shouldCloseOnEsc: false, shouldCloseOnEsc: false,
isOpen: true, isOpen: true,
onClose: null,
closeButton: true, closeButton: true,
title: null, title: null,
customClassName: '', customClassName: '',
@ -73,6 +74,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
content: ModalContent, content: ModalContent,
contentProps, contentProps,
isOpen, isOpen,
onClose,
title, title,
customClassName, customClassName,
shouldCloseOnEsc, shouldCloseOnEsc,
@ -88,7 +90,13 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
isOpen={isOpen} isOpen={isOpen}
title={title} title={title}
closeButton={closeButton} closeButton={closeButton}
onClose={hide} onClose={() => {
if (onClose) {
onClose();
}
hide();
}}
> >
<ModalContent {...contentProps} show={show} hide={hide} /> <ModalContent {...contentProps} show={show} hide={hide} />
</Modal> </Modal>

View File

@ -51,3 +51,9 @@ button.close
position: relative; position: relative;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
.btn-sm
padding: .25rem .5rem;
font-size: .875rem;
line-height: 1.5;
border-radius: .2rem;

View File

@ -29,6 +29,7 @@ import {
Tooltip, Tooltip,
AboutContent, AboutContent,
OHIFModal, OHIFModal,
ErrorBoundary
} from './components'; } from './components';
import { useDebounce, useMedia } from './hooks'; import { useDebounce, useMedia } from './hooks';
@ -123,6 +124,7 @@ export {
DialogProvider, DialogProvider,
withDialog, withDialog,
useDialog, useDialog,
ErrorBoundary,
// Hooks // Hooks
useDebounce, useDebounce,
useMedia, useMedia,

View File

@ -3,6 +3,247 @@
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.5.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.2...@ohif/viewer@4.5.3) (2020-08-28)
**Note:** Version bump only for package @ohif/viewer
## [4.5.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.1...@ohif/viewer@4.5.2) (2020-08-24)
**Note:** Version bump only for package @ohif/viewer
## [4.5.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.0...@ohif/viewer@4.5.1) (2020-08-20)
**Note:** Version bump only for package @ohif/viewer
# [4.5.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.4.1...@ohif/viewer@4.5.0) (2020-08-20)
### Features
* 🎸 Filter/promote multiple series instances ([#1533](https://github.com/OHIF/Viewers/issues/1533)) ([5fdace1](https://github.com/OHIF/Viewers/commit/5fdace1432b4b1bd31a60c5db6bd681610ec2b8e))
## [4.4.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.4.0...@ohif/viewer@4.4.1) (2020-08-20)
### Bug Fixes
* Fix incorrect command name in Percy test ([#1999](https://github.com/OHIF/Viewers/issues/1999)) ([ebdcde1](https://github.com/OHIF/Viewers/commit/ebdcde1c4d9c95393cf79cc9994f5d60f6d66fdd))
# [4.4.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.3.1...@ohif/viewer@4.4.0) (2020-08-18)
### Features
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
## [4.3.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.3.0...@ohif/viewer@4.3.1) (2020-08-10)
**Note:** Version bump only for package @ohif/viewer
# [4.3.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.16...@ohif/viewer@4.3.0) (2020-08-10)
### Features
* 🎸 Optional disable measurements panel in app config ([#1912](https://github.com/OHIF/Viewers/issues/1912)) ([3d86b5f](https://github.com/OHIF/Viewers/commit/3d86b5f876d6481bcc57de88a0ba1f974f4ee263)), closes [#1864](https://github.com/OHIF/Viewers/issues/1864)
## [4.2.16](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.15...@ohif/viewer@4.2.16) (2020-08-10)
**Note:** Version bump only for package @ohif/viewer
## [4.2.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.14...@ohif/viewer@4.2.15) (2020-08-05)
**Note:** Version bump only for package @ohif/viewer
## [4.2.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.13...@ohif/viewer@4.2.14) (2020-08-05)
**Note:** Version bump only for package @ohif/viewer
## [4.2.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.12...@ohif/viewer@4.2.13) (2020-07-23)
**Note:** Version bump only for package @ohif/viewer
## [4.2.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.11...@ohif/viewer@4.2.12) (2020-07-22)
### Bug Fixes
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
## [4.2.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.10...@ohif/viewer@4.2.11) (2020-07-13)
### Bug Fixes
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
## [4.2.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.9...@ohif/viewer@4.2.10) (2020-07-13)
**Note:** Version bump only for package @ohif/viewer
## [4.2.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.8...@ohif/viewer@4.2.9) (2020-07-13)
**Note:** Version bump only for package @ohif/viewer
## [4.2.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.7...@ohif/viewer@4.2.8) (2020-07-13)
### Bug Fixes
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
## [4.2.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.6...@ohif/viewer@4.2.7) (2020-06-18)
**Note:** Version bump only for package @ohif/viewer
## [4.2.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.5...@ohif/viewer@4.2.6) (2020-06-18)
**Note:** Version bump only for package @ohif/viewer
## [4.2.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.4...@ohif/viewer@4.2.5) (2020-06-15)
### Bug Fixes
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
## [4.2.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.3...@ohif/viewer@4.2.4) (2020-06-15)
### Bug Fixes
* OIDC Redirect erases query parameters ([#1773](https://github.com/OHIF/Viewers/issues/1773)) ([6123741](https://github.com/OHIF/Viewers/commit/6123741765e81d0bea8fbd5dbb0f310aaca0fb33))
## [4.2.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.2...@ohif/viewer@4.2.3) (2020-06-05)
**Note:** Version bump only for package @ohif/viewer
## [4.2.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.1...@ohif/viewer@4.2.2) (2020-06-04)
**Note:** Version bump only for package @ohif/viewer
## [4.2.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.2.0...@ohif/viewer@4.2.1) (2020-06-04)
**Note:** Version bump only for package @ohif/viewer
# [4.2.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.1.0...@ohif/viewer@4.2.0) (2020-06-04)
### Features
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
# [4.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.0.0...@ohif/viewer@4.1.0) (2020-05-15)
### Features
* expose some app internals as window.app ([#1735](https://github.com/OHIF/Viewers/issues/1735)) ([63fd656](https://github.com/OHIF/Viewers/commit/63fd65690cba450721870a6222e0fb3ad71bb291))
# [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14) # [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14)

View File

@ -115,13 +115,13 @@ Commands to run the tests:
```js ```js
// Open Cypress Dashboard that provides insight into what happened when your tests ran // Open Cypress Dashboard that provides insight into what happened when your tests ran
yarn run cy yarn test:e2e
// Run all tests using Electron browser headless // Run all tests using Electron browser headless
yarn run cy:run yarn test:e2e:local
// Run all tests in CI mode // Run all tests in CI mode
yarn run cy:run:ci yarn run test:e2e:ci
``` ```
### Contributing ### Contributing

View File

@ -5,17 +5,18 @@ describe('OHIF Routes', function() {
cy.openStudyList(); cy.openStudyList();
}); });
it('checks TEST json url study route', function() { // TODO -> Bring back when testJSON is hosted again.
cy.visit( // it('checks TEST json url study route', function() {
'/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json' // cy.visit(
); // '/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json'
// );
cy.server(); // cy.server();
cy.route('GET', '**/MRStudy/**').as('getTESTStudy'); // cy.route('GET', '**/ MRStudy; /**').as('getTESTStudy');
cy.wait('@getTESTStudy.all'); // cy.wait('@getTESTStudy.all');
cy.get('@getTESTStudy').should($route => { // cy.get('@getTESTStudy').should($route => {
expect($route.status).to.be.eq(200); // expect($route.status).to.be.eq(200);
}); // });
}); // });
}); });

View File

@ -38,12 +38,12 @@ describe('OHIF Study List', function() {
}); });
it('searches Accession with exact string', function() { it('searches Accession with exact string', function() {
cy.get('@AccessionNumber').type('fpcben98890'); cy.get('@AccessionNumber').type('0000481914');
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1); expect($list.length).to.be.eq(1);
expect($list).to.contain('fpcben98890'); expect($list).to.contain('0000481914');
}); });
}); });
@ -52,7 +52,8 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR? // TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
expect($list.length).to.be.eq(9);
expect($list).to.contain('MR'); expect($list).to.contain('MR');
}); });
}); });
@ -147,18 +148,19 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR? // TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
expect($list.length).to.be.eq(9);
expect($list).to.contain('MR'); expect($list).to.contain('MR');
}); });
}); });
it('searches Accession with exact string', function() { it('searches Accession with exact string', function() {
cy.get('@accessionModalityDescription').type('fpcben98890'); cy.get('@accessionModalityDescription').type('0000481914');
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(1); expect($list.length).to.be.eq(1);
expect($list).to.contain('fpcben98890'); expect($list).to.contain('0000481914');
}); });
}); });

View File

@ -1,7 +1,7 @@
describe('OHIF Microscopy Extension', () => { describe('OHIF Microscopy Extension', () => {
before(() => { before(() => {
cy.openStudyModality('SM'); cy.openStudyModality('SM');
cy.expectMinimumThumbnails(6); cy.expectMinimumThumbnails(2);
}); });
it('checks if series thumbnails are being displayed', () => { it('checks if series thumbnails are being displayed', () => {
@ -12,6 +12,8 @@ describe('OHIF Microscopy Extension', () => {
}); });
it('drags and drop a SM thumbnail into viewport', () => { it('drags and drop a SM thumbnail into viewport', () => {
// Waiting for series list to load all displaySets (lots of SRs, before defining which dom element to grab.)
cy.wait(3000);
cy.get('[data-cy="thumbnail-list"]') cy.get('[data-cy="thumbnail-list"]')
.contains('SM') .contains('SM')
.drag('.viewport-drop-target'); .drag('.viewport-drop-target');

View File

@ -19,7 +19,7 @@ describe('Visual Regression - OHIF VTK Extension', () => {
cy.get('[data-cy="2d mpr"]').click(); cy.get('[data-cy="2d mpr"]').click();
//Wait waitVTKLoading Images //Wait waitVTKLoading Images
cy.waitVTKwaitVTKLoading(); cy.waitVTKLoading();
}); });
beforeEach(() => { beforeEach(() => {

View File

@ -5,13 +5,15 @@ describe('Visual Regression - OHIF Routes', function() {
cy.openStudyList(); cy.openStudyList();
}); });
/*
// TODO -> Bring back when testJSON is hosted again.
it('checks TEST json url study route', function() { it('checks TEST json url study route', function() {
cy.visit( cy.visit(
'/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json' '/viewer?url=https://ohif-viewer.s3.eu-central-1.amazonaws.com/JSON/testJSON.json'
); );
cy.server(); cy.server();
cy.route('GET', '**/TESTStudy/**').as('getTESTStudy'); cy.route('GET', '**/ TESTStudy; /**').as('getTESTStudy');
cy.wait('@getTESTStudy.all'); cy.wait('@getTESTStudy.all');
cy.get('@getTESTStudy').should($route => { cy.get('@getTESTStudy').should($route => {
@ -20,4 +22,5 @@ describe('Visual Regression - OHIF Routes', function() {
cy.percyCanvasSnapshot('TEST json study route'); cy.percyCanvasSnapshot('TEST json study route');
}); });
*/
}); });

View File

@ -109,12 +109,12 @@ Cypress.Commands.add('waitStudyList', () => {
Cypress.Commands.add('waitVTKLoading', () => { Cypress.Commands.add('waitVTKLoading', () => {
// Wait for start loading // Wait for start loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => { cy.get('[data-cy="viewprt-grid"]', { timeout: 20000 }).should($grid => {
expect($grid).to.contain.text('Loading'); expect($grid).to.contain.text('Loading');
}); });
// Wait for finish loading // Wait for finish loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => { cy.get('[data-cy="viewprt-grid"]', { timeout: 90000 }).should($grid => {
expect($grid).not.to.contain.text('Loading'); expect($grid).not.to.contain.text('Loading');
}); });
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.0.0", "version": "4.5.3",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@ -48,26 +48,26 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.9.6", "@ohif/core": "^2.10.1",
"@ohif/extension-cornerstone": "^2.7.3", "@ohif/extension-cornerstone": "^2.9.2",
"@ohif/extension-dicom-html": "^1.2.2", "@ohif/extension-dicom-html": "^1.2.5",
"@ohif/extension-dicom-microscopy": "^0.50.9", "@ohif/extension-dicom-microscopy": "^0.51.1",
"@ohif/extension-dicom-p10-downloader": "^0.1.0", "@ohif/extension-dicom-p10-downloader": "^0.1.2",
"@ohif/extension-dicom-pdf": "^1.0.3", "@ohif/extension-dicom-pdf": "^1.0.4",
"@ohif/extension-dicom-rt": "^0.1.4", "@ohif/extension-dicom-rt": "^0.4.1",
"@ohif/extension-dicom-segmentation": "^0.2.6", "@ohif/extension-dicom-segmentation": "^0.4.1",
"@ohif/extension-lesion-tracker": "^0.2.0", "@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-vtk": "^1.5.6", "@ohif/extension-vtk": "^1.7.2",
"@ohif/i18n": "^0.52.8", "@ohif/i18n": "^0.52.8",
"@ohif/ui": "^1.4.4", "@ohif/ui": "^1.5.3",
"@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.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "0.16.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4", "dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",

View File

@ -2,6 +2,7 @@ window.config = function(props) {
var servicesManager = props.servicesManager; var servicesManager = props.servicesManager;
return { return {
disableMeasurementPanel: true,
routerBasename: '/', routerBasename: '/',
enableGoogleCloudAdapter: true, enableGoogleCloudAdapter: true,
enableGoogleCloudAdapterUI: false, enableGoogleCloudAdapterUI: false,
@ -9,11 +10,10 @@ window.config = function(props) {
httpErrorHandler: error => { httpErrorHandler: error => {
// This is 429 when rejected from the public idc sandbox too often. // This is 429 when rejected from the public idc sandbox too often.
console.warn(error.status); console.warn(error.status);
// Could use services manager here to bring up a dialog/modal if needed. // Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/'); console.warn('test, navigate to https://ohif.org/');
window.location = 'https://ohif.org/'; window.location = 'https://ohif.org/';
}, },
healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1', healthcareApiEndpoint: 'https://proxy-dot-idc-dev.appspot.com/v1beta1',
}; };
}; };

View File

@ -13,6 +13,7 @@ import {
ModalProvider, ModalProvider,
DialogProvider, DialogProvider,
OHIFModal, OHIFModal,
ErrorBoundary,
} from '@ohif/ui'; } from '@ohif/ui';
import { import {
@ -71,6 +72,14 @@ let extensionManager;
// TODO[react] Use a provider when the whole tree is React // TODO[react] Use a provider when the whole tree is React
window.store = store; window.store = store;
window.ohif = window.ohif || {};
window.ohif.app = {
commandsManager,
hotkeysManager,
servicesManager,
extensionManager,
};
class App extends Component { class App extends Component {
static propTypes = { static propTypes = {
config: PropTypes.oneOfType([ config: PropTypes.oneOfType([
@ -159,53 +168,57 @@ class App extends Component {
if (this._userManager) { if (this._userManager) {
return ( return (
<Provider store={store}> <ErrorBoundary context="App">
<AppProvider config={this._appConfig}> <Provider store={store}>
<I18nextProvider i18n={i18n}> <AppProvider config={this._appConfig}>
<OidcProvider store={store} userManager={this._userManager}> <I18nextProvider i18n={i18n}>
<UserManagerContext.Provider value={this._userManager}> <OidcProvider store={store} userManager={this._userManager}>
<Router basename={routerBasename}> <UserManagerContext.Provider value={this._userManager}>
<WhiteLabelingContext.Provider value={whiteLabeling}> <Router basename={routerBasename}>
<SnackbarProvider service={UINotificationService}> <WhiteLabelingContext.Provider value={whiteLabeling}>
<DialogProvider service={UIDialogService}> <SnackbarProvider service={UINotificationService}>
<ModalProvider <DialogProvider service={UIDialogService}>
modal={OHIFModal} <ModalProvider
service={UIModalService} modal={OHIFModal}
> service={UIModalService}
<OHIFStandaloneViewer >
userManager={this._userManager} <OHIFStandaloneViewer
/> userManager={this._userManager}
</ModalProvider> />
</DialogProvider> </ModalProvider>
</SnackbarProvider> </DialogProvider>
</WhiteLabelingContext.Provider> </SnackbarProvider>
</Router> </WhiteLabelingContext.Provider>
</UserManagerContext.Provider> </Router>
</OidcProvider> </UserManagerContext.Provider>
</I18nextProvider> </OidcProvider>
</AppProvider> </I18nextProvider>
</Provider> </AppProvider>
</Provider>
</ErrorBoundary>
); );
} }
return ( return (
<Provider store={store}> <ErrorBoundary context="App">
<AppProvider config={this._appConfig}> <Provider store={store}>
<I18nextProvider i18n={i18n}> <AppProvider config={this._appConfig}>
<Router basename={routerBasename}> <I18nextProvider i18n={i18n}>
<WhiteLabelingContext.Provider value={whiteLabeling}> <Router basename={routerBasename}>
<SnackbarProvider service={UINotificationService}> <WhiteLabelingContext.Provider value={whiteLabeling}>
<DialogProvider service={UIDialogService}> <SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal} service={UIModalService}> <DialogProvider service={UIDialogService}>
<OHIFStandaloneViewer /> <ModalProvider modal={OHIFModal} service={UIModalService}>
</ModalProvider> <OHIFStandaloneViewer />
</DialogProvider> </ModalProvider>
</SnackbarProvider> </DialogProvider>
</WhiteLabelingContext.Provider> </SnackbarProvider>
</Router> </WhiteLabelingContext.Provider>
</I18nextProvider> </Router>
</AppProvider> </I18nextProvider>
</Provider> </AppProvider>
</Provider>
</ErrorBoundary>
); );
} }
@ -258,17 +271,21 @@ function _initExtensions(extensions, cornerstoneExtensionConfig, appConfig) {
api: { api: {
contexts: CONTEXTS, contexts: CONTEXTS,
hooks: { hooks: {
useAppContext useAppContext,
} },
} },
}); });
const requiredExtensions = [ const requiredExtensions = [
GenericViewerCommands, GenericViewerCommands,
[OHIFCornerstoneExtension, cornerstoneExtensionConfig], [OHIFCornerstoneExtension, cornerstoneExtensionConfig],
/* WARNING: MUST BE REGISTERED _AFTER_ OHIFCornerstoneExtension */
MeasurementsPanel,
]; ];
if (appConfig.disableMeasurementPanel !== true) {
/* WARNING: MUST BE REGISTERED _AFTER_ OHIFCornerstoneExtension */
requiredExtensions.push(MeasurementsPanel);
}
const mergedExtensions = requiredExtensions.concat(extensions); const mergedExtensions = requiredExtensions.concat(extensions);
extensionManager.registerExtensions(mergedExtensions); extensionManager.registerExtensions(mergedExtensions);
} }

View File

@ -5,7 +5,7 @@ import { Route, Switch } from 'react-router-dom';
import { NProgress } from '@tanem/react-nprogress'; import { NProgress } from '@tanem/react-nprogress';
import { CSSTransition } from 'react-transition-group'; import { CSSTransition } from 'react-transition-group';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ViewerbaseDragDropContext } from '@ohif/ui'; import { ViewerbaseDragDropContext, ErrorBoundary } from '@ohif/ui';
import { SignoutCallbackComponent } from 'redux-oidc'; import { SignoutCallbackComponent } from 'redux-oidc';
import asyncComponent from './components/AsyncComponent.js'; import asyncComponent from './components/AsyncComponent.js';
import * as RoutesUtil from './routes/routesUtil'; import * as RoutesUtil from './routes/routesUtil';
@ -52,10 +52,13 @@ class OHIFStandaloneViewer extends Component {
const { appConfig = {} } = this.context; const { appConfig = {} } = this.context;
const userNotLoggedIn = userManager && (!user || user.expired); const userNotLoggedIn = userManager && (!user || user.expired);
if (userNotLoggedIn) { if (userNotLoggedIn) {
const pathname = this.props.location.pathname; const { pathname, search } = this.props.location;
if (pathname !== '/callback') { if (pathname !== '/callback') {
sessionStorage.setItem('ohif-redirect-to', pathname); sessionStorage.setItem(
'ohif-redirect-to',
JSON.stringify({ pathname, search })
);
} }
return ( return (
@ -103,12 +106,21 @@ class OHIFStandaloneViewer extends Component {
userManager.removeUser().then(() => { userManager.removeUser().then(() => {
if (targetLinkUri !== null) { if (targetLinkUri !== null) {
const ohifRedirectTo = {
pathname: new URL(targetLinkUri).pathname,
};
sessionStorage.setItem( sessionStorage.setItem(
'ohif-redirect-to', 'ohif-redirect-to',
new URL(targetLinkUri).pathname JSON.stringify(ohifRedirectTo)
); );
} else { } else {
sessionStorage.setItem('ohif-redirect-to', '/'); const ohifRedirectTo = {
pathname: '/',
};
sessionStorage.setItem(
'ohif-redirect-to',
JSON.stringify(ohifRedirectTo)
);
} }
if (loginHint !== null) { if (loginHint !== null) {
@ -191,8 +203,10 @@ class OHIFStandaloneViewer extends Component {
{match === null ? ( {match === null ? (
<></> <></>
) : ( ) : (
<ErrorBoundary context={match.url}>
<Component match={match} location={this.props.location} /> <Component match={match} location={this.props.location} />
)} </ErrorBoundary>
)}
</CSSTransition> </CSSTransition>
)} )}
</Route> </Route>

View File

@ -329,7 +329,9 @@ const mapDispatchToProps = (dispatch, ownProps) => {
measurementApi.syncMeasurementsAndToolData(); measurementApi.syncMeasurementsAndToolData();
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element); if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
}); });
// Needs to update viewports.layout state to set layout // Needs to update viewports.layout state to set layout

View File

@ -0,0 +1,18 @@
.ErrorBoundaryDialogTitle {
margin-top: 0;
}
.ErrorBoundaryDialogButton {
outline: none;
}
.ErrorBoundaryDialogIcon {
margin-right: 5px;
width: 10px;
transform: rotate(-90deg);
vertical-align: middle;
}
.ErrorBoundaryDialogIcon.opened {
transform: rotate(0deg);
}

View File

@ -0,0 +1,72 @@
import React, { useState } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { ErrorBoundary, Icon } from '@ohif/ui';
import { servicesManager } from './../../App';
import './ErrorBoundaryDialog.css';
const { UIModalService } = servicesManager.services;
const ErrorBoundaryDialog = ({ context, children }) => {
const handleOnError = (error, componentStack) => {
const ErrorDialog = () => {
const [open, setOpen] = useState(false);
return (
<div className="ErrorFallback" role="alert">
<div className="ErrorBoundaryDialog">
<h3 className="ErrorBoundaryDialogTitle">
{context}: <span>{error.message}</span>
</h3>
</div>
<button
className="btn btn-primary btn-sm ErrorBoundaryDialogButton"
onClick={() => setOpen(s => !s)}
>
<Icon
name="chevron-down"
className={classnames('ErrorBoundaryDialogIcon', {
opened: open,
})}
/>
Stack Trace
</button>
{open && <pre>{componentStack}</pre>}
</div>
);
};
UIModalService.show({
content: ErrorDialog,
title: `Something went wrong in ${context}`,
});
};
const fallbackComponent = () => (
<div className="ErrorFallback" role="alert">
<p>
Error rendering {context}. <br /> Check the browser console for more
details.
</p>
</div>
);
return (
<ErrorBoundary
fallbackComponent={fallbackComponent}
context={context}
onError={handleOnError}
>
{children}
</ErrorBoundary>
);
};
ErrorBoundaryDialog.propTypes = {
context: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
};
export default ErrorBoundaryDialog;

View File

@ -0,0 +1,3 @@
import ErrorBoundaryDialog from './ErrorBoundaryDialog';
export default ErrorBoundaryDialog;

View File

@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils; const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function (props) { const ViewportGrid = function(props) {
const { const {
activeViewportIndex, activeViewportIndex,
availablePlugins, availablePlugins,
@ -24,7 +24,7 @@ const ViewportGrid = function (props) {
studies, studies,
viewportData, viewportData,
children, children,
isStudyLoaded isStudyLoaded,
} = props; } = props;
const rowSize = 100 / numRows; const rowSize = 100 / numRows;
@ -54,7 +54,7 @@ const ViewportGrid = function (props) {
}); });
}); });
} }
}, [studies, viewportData, isStudyLoaded]); }, [studies, viewportData, isStudyLoaded, snackbar]);
const getViewportPanes = () => const getViewportPanes = () =>
layout.viewports.map((layout, viewportIndex) => { layout.viewports.map((layout, viewportIndex) => {
@ -80,6 +80,7 @@ const ViewportGrid = function (props) {
// - When updating a panel, ensure that the currently enabled plugin // - When updating a panel, ensure that the currently enabled plugin
// in the viewport is capable of rendering this display set. If not // in the viewport is capable of rendering this display set. If not
// then use the most capable available plugin // then use the most capable available plugin
const pluginName = const pluginName =
!layout.plugin && displaySet && displaySet.plugin !layout.plugin && displaySet && displaySet.plugin
? displaySet.plugin ? displaySet.plugin

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import './ViewportPane.css'; import './ViewportPane.css';
const ViewportPane = function(props) { const ViewportPane = function (props) {
const { children, onDrop, viewportIndex, className: propClassName } = props; const { children, onDrop, viewportIndex, className: propClassName } = props;
const [{ hovered, highlighted }, drop] = useDrop({ const [{ hovered, highlighted }, drop] = useDrop({
accept: 'thumbnail', accept: 'thumbnail',

View File

@ -4,6 +4,8 @@ import { StudyBrowser } from '@ohif/ui';
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
import findDisplaySetByUID from './findDisplaySetByUID'; import findDisplaySetByUID from './findDisplaySetByUID';
const { studyMetadataManager } = OHIF.utils;
const { setActiveViewportSpecificData } = OHIF.redux.actions; const { setActiveViewportSpecificData } = OHIF.redux.actions;
// TODO // TODO
@ -39,11 +41,27 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch, ownProps) => {
return { return {
onThumbnailClick: displaySetInstanceUID => { onThumbnailClick: displaySetInstanceUID => {
const displaySet = findDisplaySetByUID( let displaySet = findDisplaySetByUID(
ownProps.studyMetadata, ownProps.studyMetadata,
displaySetInstanceUID displaySetInstanceUID
); );
if (displaySet.isDerived) {
const { Modality } = displaySet;
displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata);
if (!displaySet) {
throw new Error(
`Referenced series for ${Modality} dataset not present.`
);
}
if (!displaySet) {
throw new Error('Source data not present');
}
}
dispatch(setActiveViewportSpecificData(displaySet)); dispatch(setActiveViewportSpecificData(displaySet));
}, },
}; };

View File

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js'; import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
const { clearViewportSpecificData } = OHIF.redux.actions; const { clearViewportSpecificData, setStudyData } = OHIF.redux.actions;
const isActive = a => a.active === true; const isActive = a => a.active === true;
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
@ -14,6 +14,9 @@ const mapStateToProps = (state, ownProps) => {
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
setStudyData: (StudyInstanceUID, data) => {
dispatch(setStudyData(StudyInstanceUID, data));
},
clearViewportSpecificData: () => { clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData()); dispatch(clearViewportSpecificData());
}, },

View File

@ -2,8 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { MODULE_TYPES } from '@ohif/core'; import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core';
import OHIF, { 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';
@ -11,6 +10,7 @@ import ToolbarRow from './ToolbarRow.js';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js'; import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js'; import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js'; import SidePanel from './../components/SidePanel.js';
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
import { extensionManager } from './../App.js'; import { extensionManager } from './../App.js';
// Contexts // Contexts
@ -19,6 +19,7 @@ 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';
class Viewer extends Component { class Viewer extends Component {
static propTypes = { static propTypes = {
@ -26,6 +27,7 @@ class Viewer extends Component {
PropTypes.shape({ PropTypes.shape({
StudyInstanceUID: PropTypes.string.isRequired, StudyInstanceUID: PropTypes.string.isRequired,
StudyDate: PropTypes.string, StudyDate: PropTypes.string,
PatientID: PropTypes.string,
displaySets: PropTypes.arrayOf( displaySets: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
displaySetInstanceUID: PropTypes.string.isRequired, displaySetInstanceUID: PropTypes.string.isRequired,
@ -188,6 +190,7 @@ class Viewer extends Component {
currentTimepointId, currentTimepointId,
]); ]);
} }
this.setState({ this.setState({
thumbnails: _mapStudiesToThumbnails(studies), thumbnails: _mapStudiesToThumbnails(studies),
}); });
@ -196,6 +199,7 @@ class Viewer extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { studies, isStudyLoaded } = this.props; const { studies, isStudyLoaded } = this.props;
if (studies !== prevProps.studies) { if (studies !== prevProps.studies) {
this.setState({ this.setState({
thumbnails: _mapStudiesToThumbnails(studies), thumbnails: _mapStudiesToThumbnails(studies),
@ -256,42 +260,44 @@ class Viewer extends Component {
</WhiteLabelingContext.Consumer> </WhiteLabelingContext.Consumer>
{/* TOOLBAR */} {/* TOOLBAR */}
<ToolbarRow <ErrorBoundaryDialog context="ToolbarRow">
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen} <ToolbarRow
isRightSidePanelOpen={this.state.isRightSidePanelOpen} isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
selectedLeftSidePanel={ isRightSidePanelOpen={this.state.isRightSidePanelOpen}
this.state.isLeftSidePanelOpen selectedLeftSidePanel={
? this.state.selectedLeftSidePanel this.state.isLeftSidePanelOpen
: '' ? this.state.selectedLeftSidePanel
} : ''
selectedRightSidePanel={
this.state.isRightSidePanelOpen
? this.state.selectedRightSidePanel
: ''
}
handleSidePanelChange={(side, selectedPanel) => {
const sideClicked = side && side[0].toUpperCase() + side.slice(1);
const openKey = `is${sideClicked}SidePanelOpen`;
const selectedKey = `selected${sideClicked}SidePanel`;
const updatedState = Object.assign({}, this.state);
const isOpen = updatedState[openKey];
const prevSelectedPanel = updatedState[selectedKey];
// RoundedButtonGroup returns `null` if selected button is clicked
const isSameSelectedPanel =
prevSelectedPanel === selectedPanel || selectedPanel === null;
updatedState[selectedKey] = selectedPanel || prevSelectedPanel;
const isClosedOrShouldClose = !isOpen || isSameSelectedPanel;
if (isClosedOrShouldClose) {
updatedState[openKey] = !updatedState[openKey];
} }
selectedRightSidePanel={
this.state.isRightSidePanelOpen
? this.state.selectedRightSidePanel
: ''
}
handleSidePanelChange={(side, selectedPanel) => {
const sideClicked = side && side[0].toUpperCase() + side.slice(1);
const openKey = `is${sideClicked}SidePanelOpen`;
const selectedKey = `selected${sideClicked}SidePanel`;
const updatedState = Object.assign({}, this.state);
this.setState(updatedState); const isOpen = updatedState[openKey];
}} const prevSelectedPanel = updatedState[selectedKey];
studies={this.props.studies} // RoundedButtonGroup returns `null` if selected button is clicked
/> const isSameSelectedPanel =
prevSelectedPanel === selectedPanel || selectedPanel === null;
updatedState[selectedKey] = selectedPanel || prevSelectedPanel;
const isClosedOrShouldClose = !isOpen || isSameSelectedPanel;
if (isClosedOrShouldClose) {
updatedState[openKey] = !updatedState[openKey];
}
this.setState(updatedState);
}}
studies={this.props.studies}
/>
</ErrorBoundaryDialog>
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/} {/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
{/*<StudyPrefetcher studies={this.props.studies} />*/} {/*<StudyPrefetcher studies={this.props.studies} />*/}
@ -299,37 +305,46 @@ class Viewer extends Component {
{/* VIEWPORTS + SIDEPANELS */} {/* VIEWPORTS + SIDEPANELS */}
<div className="FlexboxLayout"> <div className="FlexboxLayout">
{/* LEFT */} {/* LEFT */}
<SidePanel from="left" isOpen={this.state.isLeftSidePanelOpen}> <ErrorBoundaryDialog context="LeftSidePanel">
{VisiblePanelLeft ? ( <SidePanel from="left" isOpen={this.state.isLeftSidePanelOpen}>
<VisiblePanelLeft {VisiblePanelLeft ? (
viewports={this.props.viewports} <VisiblePanelLeft
studies={this.props.studies} viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex} studies={this.props.studies}
/> activeIndex={this.props.activeViewportIndex}
) : ( />
) : (
<ConnectedStudyBrowser <ConnectedStudyBrowser
studies={this.state.thumbnails} studies={this.state.thumbnails}
studyMetadata={this.props.studies} studyMetadata={this.props.studies}
/> />
)} )}
</SidePanel> </SidePanel>
</ErrorBoundaryDialog>
{/* MAIN */} {/* MAIN */}
<div className={classNames('main-content')}> <div className={classNames('main-content')}>
<ConnectedViewerMain studies={this.props.studies} isStudyLoaded={this.props.isStudyLoaded} /> <ErrorBoundaryDialog context="ViewerMain">
<ConnectedViewerMain
studies={this.props.studies}
isStudyLoaded={this.props.isStudyLoaded}
/>
</ErrorBoundaryDialog>
</div> </div>
{/* RIGHT */} {/* RIGHT */}
<SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}> <ErrorBoundaryDialog context="RightSidePanel">
{VisiblePanelRight && ( <SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}>
<VisiblePanelRight {VisiblePanelRight && (
isOpen={this.state.isRightSidePanelOpen} <VisiblePanelRight
viewports={this.props.viewports} isOpen={this.state.isRightSidePanelOpen}
studies={this.props.studies} viewports={this.props.viewports}
activeIndex={this.props.activeViewportIndex} studies={this.props.studies}
/> activeIndex={this.props.activeViewportIndex}
)} />
</SidePanel> )}
</SidePanel>
</ErrorBoundaryDialog>
</div> </div>
</> </>
); );
@ -343,13 +358,12 @@ export default withDialog(Viewer);
* a mapping layer? * a mapping layer?
* *
* TODO[react]: * TODO[react]:
* - Add sorting of display sets
* - Add showStackLoadingProgressBar option * - Add showStackLoadingProgressBar option
* *
* @param {Study[]} studies * @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets * @param {DisplaySet[]} studies[].displaySets
*/ */
const _mapStudiesToThumbnails = function (studies) { const _mapStudiesToThumbnails = function(studies) {
return studies.map(study => { return studies.map(study => {
const { StudyInstanceUID } = study; const { StudyInstanceUID } = study;
@ -357,9 +371,9 @@ const _mapStudiesToThumbnails = function (studies) {
const { const {
displaySetInstanceUID, displaySetInstanceUID,
SeriesDescription, SeriesDescription,
SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames, numImageFrames,
SeriesNumber,
} = displaySet; } = displaySet;
let imageId; let imageId;
@ -383,9 +397,9 @@ const _mapStudiesToThumbnails = function (studies) {
altImageText, altImageText,
displaySetInstanceUID, displaySetInstanceUID,
SeriesDescription, SeriesDescription,
SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames, numImageFrames,
SeriesNumber,
}; };
}); });

View File

@ -77,7 +77,8 @@ class ViewerLocalFileData extends Component {
study.displaySets = study.displaySets =
study.displaySets || study.displaySets ||
studyMetadata.createDisplaySets(sopClassHandlerModules); studyMetadata.createDisplaySets(sopClassHandlerModules);
studyMetadata.setDisplaySets(study.displaySets);
debugger;
studyMetadata.forEachDisplaySet(displayset => { studyMetadata.forEachDisplaySet(displayset => {
displayset.localFile = true; displayset.localFile = true;

Some files were not shown because too many files have changed in this diff Show More