Compare commits

..
Author SHA1 Message Date
ohif-bot 11c1d12872 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.8
 - @ohif/core@2.10.6
 - @ohif/viewer@4.5.15
2020-09-30 18:03:17 +00:00
Ștefan Silviu-AlexandruandStefan Silviu 91364db882 fix: use SeriesMetadata method instead of property in findMostRecentStructuredReport (#2038)
fixes #1714

Call getInstanceCount() instead of manually checking length (which does not work)

Co-authored-by: Stefan Silviu <silviu.stefan@ab4.systems>
2020-09-30 20:01:07 +02:00
Nicolas Byl bea31d5fec fix: Enable port changes for Docker image using the PORT variable (#2016) 2020-09-30 19:55:28 +02:00
ohif-bot 48ecbc261a chore(release): publish [skip ci]
- @ohif/viewer@4.5.14
2020-09-30 10:19:10 +00:00
Woonchan ChoandWoonchan Cho 8bc467fb07 [IDC-2052] PWA start url (#2070)
Change start_url in manifest.json from "./index.html" to "./"

Co-authored-by: Woonchan Cho <wcho24@wustl.edu>
2020-09-30 12:16:18 +02:00
ohif-bot cffc44f0f4 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.5
 - @ohif/viewer@4.5.13
2020-09-29 10:42:25 +00:00
James Petts ea0fc07e30 Update react-cornerstone-viewport for performance improvements (#2062)
* Update react-cornerstone-viewport

* Bump react-cornerstone-viewport version.

* Comment out tests which always cause problems.
2020-09-29 12:40:27 +02:00
ohif-bot b8d241fa33 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.0.3
 - @ohif/viewer@4.5.12
2020-09-24 10:35:44 +00:00
James Petts ccdfcd4a85 [IDC-2049] Sort Tags in Tag browser split items in sequences, add indent after space. (#2053)
* Sort tag browser, add items, add indent.

* Remove debugger.
2020-09-24 12:34:22 +02:00
ohif-bot 6cf72d1757 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.4.6
 - @ohif/extension-dicom-segmentation@0.4.6
 - @ohif/viewer@4.5.11
2020-09-17 19:11:25 +00:00
James Petts bb1cdbda3a [IDC-1905] Hover tooltips for Segmentation + RTSTRUCT extension (#2044)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests

* WIP

* WIP

* Finish mailTo

* tooltop
2020-09-17 21:09:46 +02:00
ohif-bot fab122dd8f chore(release): publish [skip ci]
- @ohif/extension-debugging@0.1.5
 - @ohif/extension-vtk@1.7.7
 - @ohif/ui@1.5.5
 - @ohif/viewer@4.5.10
2020-09-17 18:31:52 +00:00
James Petts 42803b0ae8 [IDC-2006] - optional mailTo for debugging extension. (#2027)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests

* WIP

* WIP

* Finish mailTo
2020-09-17 20:28:08 +02:00
ohif-bot 5a78da8ff6 chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.2.8
 - @ohif/extension-dicom-microscopy@0.51.3
 - @ohif/extension-dicom-pdf@1.0.6
 - @ohif/extension-dicom-rt@0.4.5
 - @ohif/extension-dicom-segmentation@0.4.5
 - @ohif/extension-dicom-tag-browser@0.0.2
 - @ohif/extension-vtk@1.7.6
 - @ohif/core@2.10.5
 - @ohif/ui@1.5.4
 - @ohif/viewer@4.5.9
2020-09-10 10:17:12 +00:00
James Petts 0ef88e88eb [IDC-1129] - DICOM Tag Viewer extension (#2022)
* WIP render top level tags.

* Add drop down to select series.

* Fix errors with type 2 sequences.

* WIP swap instance.

* Fix formatting and make fullscreen.

* Remove debuggers.

* Finish formatting.

* Fix error with double SeriesNumber deconstruction.

* Address reviewer comments.
2020-09-10 12:13:24 +02:00
ohif-bot a7b5f1847a chore(release): publish [skip ci]
- @ohif/extension-debugging@0.1.4
 - @ohif/extension-dicom-rt@0.4.4
 - @ohif/extension-dicom-segmentation@0.4.4
 - @ohif/viewer@4.5.8
2020-09-10 09:14:18 +00:00
James Petts 5cb6e63b88 [IDC-1672] Highlight SEG segment/ RT structure when click to jump. (#2034)
* Highlight for RTSTRUCT.

* SEG temp crosshairs.

* Disable RT highlighting for now.

* Remove TODO
2020-09-10 11:11:59 +02:00
ohif-bot 48eb4a9ec3 chore(release): publish [skip ci]
- @ohif/viewer@4.5.7
2020-09-09 13:00:24 +00:00
James Petts d9231fd431 Split query parameter calls. (#2035) 2020-09-09 14:59:07 +02:00
ohif-bot e5b578bd8e chore(release): publish [skip ci]
- @ohif/extension-vtk@1.7.5
 - @ohif/core@2.10.4
 - @ohif/viewer@4.5.6
2020-09-03 13:51:56 +00:00
James Petts f38979037d IDC-1532 multiple series search google (#2026)
* Multiple series search for google cloud adapter.

* Revert IDC config.

* fix: 🐛 Series filtering on multiple series for google

* Revert changes to default config.

* Address reviewers comments.

* Fixed spelling mistakes
2020-09-03 15:48:32 +02:00
ohif-bot 45ca88f5b8 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.4
 - @ohif/extension-debugging@0.1.3
 - @ohif/extension-dicom-html@1.2.7
 - @ohif/extension-dicom-microscopy@0.51.2
 - @ohif/extension-dicom-pdf@1.0.5
 - @ohif/extension-dicom-rt@0.4.3
 - @ohif/extension-dicom-segmentation@0.4.3
 - @ohif/extension-lesion-tracker@0.2.1
 - @ohif/extension-vtk@1.7.4
 - @ohif/core@2.10.3
 - @ohif/viewer@4.5.5
2020-09-03 11:00:46 +00:00
James Petts a1dee16a3d [IDC-1939] Debug Dialog part 1 (#2011)
* WIP debug dialog

* Rename the p10 downloader extension to debugger extension, add button to toolbar. Deactivate it by default.

* Fix unit tests
2020-09-03 12:58:04 +02:00
ohif-bot 6a37a2a661 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.9.3
 - @ohif/extension-dicom-html@1.2.6
 - @ohif/extension-dicom-rt@0.4.2
 - @ohif/extension-dicom-segmentation@0.4.2
 - @ohif/extension-vtk@1.7.3
 - @ohif/core@2.10.2
 - @ohif/viewer@4.5.4
2020-09-02 12:19:23 +00:00
James Petts a8caf4bba4 [IDC-2017] Harden segmentation import to support more SEGs (#2024)
* fix: 🐛 Upgrade dcmjs version to support more SEGs
2020-09-02 14:17:30 +02:00
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 17:51:08 +02: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 11:44:09 +02: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 BroaddusandDanny Brown 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
ladeirarodolfoandRodolfo Ladeira 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 BroaddusandDanny Brown 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 PettsandErik Ziegler 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 14:16:19 +02: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 11:40:11 +02: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 11:20:31 +02: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 11:46:42 +02: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 11:00:47 +02: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 18:02:23 +02: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 17:35:55 +02: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 16:29:29 +02: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 14:51:33 +02: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 AntinarelliandJames Petts 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 10:59:07 +02: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 09:24:39 +02: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 OctavianoandJames Petts 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 17:41:36 +02:00
ohif-bot b7d47383c5 chore(release): publish [skip ci]
- @ohif/viewer@4.2.4
2020-06-15 12:47:19 +00:00
Rodrigo AntinarelliandJames Petts 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 14:43:10 +02: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 AntinarelliandJames Petts 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 18:00:36 +02: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 OliveiraandJames Petts 62a2cd5ff3 Fix: #1130 Fixing support for download of an entire study (#1753)
Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-06-04 13:01:05 +02: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 OctavianoandJames Petts 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 12:25:19 +02: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
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 11:52:30 +02:00
ohif-bot f01640d009 chore(release): publish [skip ci]
- @ohif/viewer@4.1.0
2020-05-15 18:59:23 +00:00
Steve Pieperanddannyrb 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
ohif-bot 944afe4a3e chore(release): publish [skip ci]
- @ohif/viewer@4.0.0
2020-05-14 14:24:17 +00:00
Igor OctavianoandJames Petts b1678ce639 fix: 🐛 Fix race condition when loading derived display sets (#1718)
When we have multiple SEGs/RTSTRUCTs we intend to load the most recent
by default.

BREAKING CHANGE: 🧨 However we start to load once the first set of metadata arrives. We need
to wait until all series metadata is fetched.

✅ Closes: #1715

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-14 16:20:23 +02:00
ohif-bot 39937cca08 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.1.4
 - @ohif/extension-vtk@1.5.6
 - @ohif/core@2.9.6
 - @ohif/viewer@3.11.11
2020-05-14 11:53:42 +00:00
Igor OctavianoandJames Petts f7b8b6a41c fix: 🐛 Load default display set when no time metadata (#1684)
* fix: 🐛 Load default display set when no time metadata

Load default displayset (first item in the array) when no series date or
series time availalbe

✅ Closes: #1683

* Add batch update

* Fix tests.

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-14 13:51:57 +02:00
ohif-bot 79fe442293 chore(release): publish [skip ci]
- @ohif/viewer@3.11.10
2020-05-13 14:03:09 +00:00
Danny Brown 875238e5b0 chore: add fuzzymatching to default server configs
Add fuzzymatching to default server configs
2020-05-13 10:00:55 -04:00
Erik Ziegler d2d3ca2828 Add fuzzymatching to default server configs 2020-05-13 15:58:29 +02:00
ohif-bot cebea832a3 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.3
 - @ohif/extension-dicom-rt@0.1.3
 - @ohif/extension-dicom-segmentation@0.2.6
 - @ohif/extension-vtk@1.5.5
 - @ohif/core@2.9.5
 - @ohif/viewer@3.11.9
2020-05-12 14:49:58 +00:00
James Petts c4f84b1174 fix: 🐛 Fix seg color load (#1724) 2020-05-12 16:47:35 +02:00
ohif-bot f9fe4232e7 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.2.5
 - @ohif/extension-vtk@1.5.4
 - @ohif/core@2.9.4
 - @ohif/viewer@3.11.8
2020-05-06 17:20:22 +00:00
Igor Octaviano 42c22df1b6 Segmentation UI for VTKjs (#1685)
* Add single viewport configuration

* Multiple viewport configuration

* Improve performance by using independent set methods

* Add jump to slice command

* Add context configuration

* Cache panel visibility

* Fix sync between vtk and cornerstone

* Remove apis index

* Add approach

* Add loading to update volumes

* Fix broken configuration

* Bump vtk version

* Use loading label

* Update cy tests after vtk loading label changed

* Remove loading for segs
2020-05-06 19:17:44 +02:00
ohif-bot 19a8e71317 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.2.4
 - @ohif/viewer@3.11.7
2020-05-04 14:46:58 +00:00
James Petts fa95bae6ef Support initialization of segment color from RecommendedDisplayCIELabValue (#1709)
* fix: 🐛 Proper error handling for derived display sets

* Revert idc.js change

* Load correct Segment colors.
2020-05-04 16:43:09 +02:00
ohif-bot 8a20a0526e chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.2
 - @ohif/extension-dicom-html@1.2.2
 - @ohif/extension-dicom-rt@0.1.2
 - @ohif/extension-dicom-segmentation@0.2.3
 - @ohif/extension-vtk@1.5.3
 - @ohif/core@2.9.3
 - @ohif/ui@1.4.4
 - @ohif/viewer@3.11.6
2020-05-04 14:15:22 +00:00
James Petts 5b20d8f323 fix: 🐛 Proper error handling for derived display sets (#1708)
* fix: 🐛 Proper error handling for derived display sets

* Revert idc.js change
2020-05-04 16:10:39 +02:00
ohif-bot 9ea37a2201 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.1
 - @ohif/viewer@3.11.5
2020-05-04 12:37:30 +00:00
Igor OctavianoandJames Petts 2a66e175df Update viewport state after moving the stack (#1645)
* Update viewport state after moving the stack

* Only trigger new imagecallback if index bigger than 0

* Fix cornerstone viewport version

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-05-04 14:30:51 +02:00
ohif-bot 77a421cdc0 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.5.2
 - @ohif/core@2.9.2
 - @ohif/viewer@3.11.4
2020-05-04 08:15:19 +00:00
Sanders DeNardiandSanders DeNardi 7033886697 fix: use bit-appropriate array for palette lookup tables (#1698)
Co-authored-by: Sanders DeNardi <sdenardi@summusglobal.com>
2020-05-04 10:13:04 +02:00
ohif-bot 256563d7ee chore(release): publish [skip ci]
- @ohif/viewer@3.11.3
2020-04-29 12:56:38 +00:00
Erik Ziegler f7162ce617 fix: Add IHEInvokeImageDisplay routes back into viewer (#1695) 2020-04-29 08:54:40 -04:00
ohif-bot 027ef4f93c chore(release): publish [skip ci]
- @ohif/extension-dicom-html@1.2.1
 - @ohif/extension-dicom-rt@0.1.1
 - @ohif/extension-dicom-segmentation@0.2.2
 - @ohif/extension-vtk@1.5.1
 - @ohif/core@2.9.1
 - @ohif/viewer@3.11.2
2020-04-28 10:52:19 +00:00
Khalid Williams f09ebbe2e8 chore: cleanup dcmjs imports
* Update imports in /platform

* Update dcmjs imports in /extentions
2020-04-28 06:49:57 -04:00
David Connaughtonandeolasd 51b4e60161 docs: updated deployment section with info for using JSON (#1676)
* update deploy section with non-dicomweb info

* fix typo and add info re. case sensitive json

* Add CORS info and reformat inline links

Co-authored-by: eolasd <pacsradwp@gmail.com>
2020-04-27 16:11:35 -04:00
ohif-bot 08de987711 chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.2.1
 - @ohif/viewer@3.11.1
2020-04-27 08:41:40 +00:00
Igor Octaviano 20e366ffbd fix: 🐛 Dropdown not updating when selecting segment (#1675)
Observe selectedSegmentation changes to refresh segment list

✅ Closes: #1669
2020-04-27 10:39:11 +02:00
ohif-bot 561911056a chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.7.0
 - @ohif/extension-dicom-html@1.2.0
 - @ohif/extension-dicom-rt@0.1.0
 - @ohif/extension-dicom-segmentation@0.2.0
 - @ohif/extension-vtk@1.5.0
 - @ohif/core@2.9.0
 - @ohif/viewer@3.11.0
2020-04-24 10:29:19 +00:00
Igor OctavianoandJames Petts 835f64d47a feat: 🎸 Seg jump to slice + show/hide
* Add segment part 2 (jump to frame and visibility toggle)

* Cr updates

* Filter displaysets with images

* feat: 🎸 Seg jump to slice + show/hide

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-04-24 12:25:05 +02:00
ohif-bot fc40d84092 chore(release): publish [skip ci]
- @ohif/viewer@3.10.2
2020-04-23 16:20:51 +00:00
Danny Brown 709f14708e fix: undefined errorHandler in cornerstoneWadoImageLoader configuration (#1664) 2020-04-23 12:17:49 -04:00
ohif-bot 5f4c4ab5e7 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.6.1
 - @ohif/extension-vtk@1.4.1
 - @ohif/core@2.8.1
 - @ohif/viewer@3.10.1
2020-04-23 10:04:47 +00:00
James Petts 71205618ec fix: 🐛 Multiframe fix (#1661) 2020-04-23 12:02:51 +02:00
ohif-bot 5014ba864b chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.6.0
 - @ohif/extension-dicom-p10-downloader@0.1.0
 - @ohif/extension-vtk@1.4.0
 - @ohif/core@2.8.0
 - @ohif/viewer@3.10.0
2020-04-23 02:06:44 +00:00
James Pettsanddannyrb e96205de35 feat: configuration to hook into XHR Error handling
* Error handling.

* Fix error handling, add redirect example.

* remove debugger

* docs: whitelabeling and httpErrorHandler

* bump dicomweb-client version

* bump cornerstone-wado-image-loader version

* Add more documentation around httpErrorHandler callback

* test: add errorHandler as an expected top level export

Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
2020-04-22 22:01:34 -04:00
ohif-bot 273f31ad56 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.14
 - @ohif/core@2.7.1
 - @ohif/viewer@3.9.2
2020-04-22 20:19:12 +00:00
Danny Brown 2093a00365 fix: whiteLabeling should support component creation by passing React to defined fn (#1659) 2020-04-22 16:16:53 -04:00
dependabot[bot] b1dad6b61b chore(deps): bump https-proxy-agent from 2.2.1 to 2.2.4 in /docs/latest (#1641)
Bumps [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) from 2.2.1 to 2.2.4.
- [Release notes](https://github.com/TooTallNate/node-https-proxy-agent/releases)
- [Commits](https://github.com/TooTallNate/node-https-proxy-agent/compare/2.2.1...2.2.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-18 22:05:44 -04:00
ohif-bot 3a587f4e4b chore(release): publish [skip ci]
- @ohif/viewer@3.9.1
2020-04-17 16:28:57 +00:00
Danny Brown d9fc7bbb0e fix: showStudyList config (#1647)
* fix: `showStudyList` config

* showStudyList should default to true if not defined
2020-04-17 12:26:09 -04:00
ohif-bot b0cfc45f83 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.13
 - @ohif/core@2.7.0
 - @ohif/viewer@3.9.0
2020-04-17 14:16:04 +00:00
Danny Brown 450c80b9d5 feat: set the authorization header for DICOMWeb requests if provided in query string (#1646) 2020-04-17 10:14:13 -04:00
ohif-bot d3eefb50f5 chore(release): publish [skip ci]
- @ohif/extension-dicom-p10-downloader@0.0.2
 - @ohif/extension-vtk@1.3.12
 - @ohif/core@2.6.11
 - @ohif/viewer@3.8.21
2020-04-15 17:00:38 +00:00
Emanuel Fiuza de OliveiraandJames Petts 29d4955980 Feat #1130 [WIP] Adding support for downloading and zipping files (#1501)
* Feat #1130 Adding support for downloading and zipping files

@ This is a combination of 12 commits.
 - Feat #1130 Initial Commit
 - Feat #1130 Adding support for hierachical lists
 - Feat #1130 [WIP] Adding support for downloading and zipping files
 - Feat #1130 Adding support for hierarchical folder structure study, series, instances on generated zip file
 - Feat #1130 Adding unit tests and addressing code review feedbacks
 - Feat #1130 POC (adding module to support on progress handling)
 - Feat #1130 Improving logic from progress tracking utils
 - Feat #1130 Renaming module with progress tracking utils
 - Feat #1130 Beta version of Progress Tracking utils
 - Feat #1130 Adding unit tests for Progress Tracking Utils
 - Feat #1130 Documenting public methods from progress tracking utils module
 - Feat #1130 Adding support for download and zip progress reporting

* Feat #1130 Addressing code review feedback

* Feat #1130 Preventing multiple downloads from executing at once

* Feat #1130 Improving error message for concurrent downloads

* Remove downloadAndZipSeriesOnViewports

Co-authored-by: James Petts <jamesapetts@gmail.com>
2020-04-15 18:57:57 +02:00
Nisal Upendra 2412c08595 docs: Embedding in existing react App (#1626)
Added how to embed OHIF viewer in an existing React application
2020-04-10 08:28:37 -04:00
ohif-bot d769e5c296 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.11
 - @ohif/core@2.6.10
 - @ohif/viewer@3.8.20
2020-04-09 11:08:07 +00:00
Danny Brown d21af3f133 fix: Revert "refactor: Reduce bundle size (#1575)" (#1622)
This reverts commit 81dcbe294d.
2020-04-09 07:06:41 -04:00
ohif-bot 70672ed663 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.5.2
 - @ohif/extension-dicom-rt@0.0.2
 - @ohif/extension-dicom-segmentation@0.1.2
 - @ohif/extension-vtk@1.3.10
 - @ohif/core@2.6.9
 - @ohif/ui@1.4.3
 - @ohif/viewer@3.8.19
2020-04-09 10:25:11 +00:00
858484f250 Support of RTSTRUCT visualization (#1524)
* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Rebase on master.

* format format format

* add basic extension parts

* Updated yarn lock

* Fetch derived/referenced displaysets in sidepanel

* Add buttons, expose seriesDescription, write notes for Danny.

* Add growcut tool extention. -> Move to a seperate repo.

* Change some text fields.

* expose dialog provider

* hide draggable area

* No pointer events for now; need to revisit

* Don't filter seg from the outset for now

* fiddling about

* fix path to series data

* fix logic to determine next available labelmapindex

* Ability to swtich between labelmaps

* Return a promise so we can make sure our labelmap is loaded before pulling metadata

* Better UI to toggle labelmaps

* Fix accidental untoggle of active labelmap for ui

* Remove brush tool from cornerstone extension

* Add cstools in preInit for seg extension

* Add buttons for seg magic

* Hide the measurements panel for now

* Slightly better seg/scissor icons

* Add eraser

* ability to switch segments

* seg color / switcher

* Fix vtk labelmap cache.

* sidepanel should scroll if there's overflow

* Use a fancier range slider

* Slightly prettier buttons

* Push in progress

* A bit more reactive; handle no-meta

* Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0

* chore: kickstart preview

* Fix conflict issues

* Refactor panel

* Isolate increment functions

* Wip segment table

* Hide selector and add scroll to segmentation

* Increase width of segment description

* Hide not used items

* Add new select for segmentations

* Align ui

* Fix default segmentation

* Fix broken import test

* Get measurements panel back and add spacing to grouped buttons

* Hide brush and selector and refresh when switching segmentations

* Add pointer events back and fix broken tests for measurement table

* Update segment list when switching humbnails

* CR Update: Minor UI adjustments

* CR Update: Set active segment

* Move components to components folder

* Add new settings ui

* Integrate configuration

* Improve styles

* Float numbers

* Improve css

* Refresh settings menu if side panel changed

* Fix settings rendering logic and remove comments

* Extract components

* Remove not used tools

* Remove non used extension

* Use style variables and remove comments

* Remove non used dep from lock

* Remove tools

* Remove segmentation from toolbar

* Add todo and brushradius from config

* Fix packagejson growcut import

* Remove webpack config already defined in parent

* Update package json description

* Change component name to british english to us

* Improve prop types

* Rename cornerstone label map event

* Scope css

* Extract and add doc to helper

* Update jsdocs for getDerivedDatasets

* Add proptypes and cr updates

* Update tablistitem proptypes

* config is readonly, rollback destructuring

* Remove event for side panel

* Add jsdocs for util

* Extract getimageid to studymetada

* Use ohif log instead of console

* Remove specific style from side panel

* Fix default props for brush radius

* Descructure configuration props from settings component to avoid invalid props

* Update proptypes to be required (settings)

* Update readme

* Remove commandsmodule

* Get configuration module inside the component

* Set selected segment

* Rename extension and use modality to filter

* Fix for failing tests (#1423)

* hello world

* Finish rebase for Igor.

* Re-fix cornerstoneTools version that got messed up in merge.

* Fix reactivity of isDisabled, write implementation for the SEG panel.

* Nuke yarn.lock to stack CST to 4.12.0

* Remove debugger statements.

* WIP consume RTSTRUCT

* Doc API and add endpoints.

* Rebase on SEG PR.

* Update DICOM Keyword variables.

* Fix metadata vars format

* Add basic RT panel along with configuration

* Wire up configuration with settings component

* Mock structure set tables

* Improve design of settings

* Integrate list of contours

* Extract panel section

* Filter by seriesuid

* Fix slow settings

* Add jump to image

* Fix expandable items

* Improvements and CR update

* CR Update: Remove google config

* CR Update: Remove erase tool test

* CR Update: Remove instance

* CR Update: Fix issue with undefined prop

* CR Update: Update yarn lock

* Hide actions of structure item

Co-authored-by: James A. Petts <jamesapetts@gmail.com>
Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com>
Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
2020-04-09 12:21:43 +02:00
ohif-bot 83f9478977 chore(release): publish [skip ci]
- @ohif/i18n@0.52.8
 - @ohif/viewer@3.8.18
2020-04-07 08:33:40 +00:00
Erik Ziegler b5b851ac7c chore(i18n): Pull latest translations from Locize to obtain French translation (thanks @salimkanoun!) (#1613)
* Pull latest translations from Locize to obtain French translation

* Fix newlines
2020-04-07 10:32:20 +02:00
ohif-bot e5175bf3e6 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.9
 - @ohif/core@2.6.8
 - @ohif/ui@1.4.2
 - @ohif/viewer@3.8.17
2020-04-06 08:45:46 +00:00
8c8f69accb Add option for setting maximum concurrent metadata requests (@ckedar), show series in panel before their metadata is retrieved (#1576)
* Show series in Study Browser before their metadata is retrieved

* Switch to decreasing for loop after code review (thanks @ckedar!)

* feat: Limit concurrent series metadata requests (#1581)

* Limit concurrent series metadata requests

* Switch to decreasing for loop after code review (thanks @ckedar!)

* Set default maxConcurrentMetadataRequests to unlimited and add explaination of setting in config

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>

* Address comments from code review

* Comment out misbehaving test

Co-authored-by: Kedar <ckedar@gmail.com>
Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-04-06 10:43:35 +02:00
ohif-bot f9037f831d chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.8
 - @ohif/core@2.6.7
 - @ohif/viewer@3.8.16
2020-04-02 17:58:41 +00:00
James PettsandDanny Brown 9e0bd52c6a fix: 🐛 Fix multiframe images (#1595)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-04-02 13:56:53 -04:00
ohif-bot f1b017dc08 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.7
 - @ohif/core@2.6.6
 - @ohif/viewer@3.8.15
2020-04-02 17:32:56 +00:00
81dcbe294d refactor: Reduce bundle size (#1575)
* async load cornerstoneWADOImageLoader chunk

* Lazy initializing of CornerstoneWADIImageLoader - delayed till Viewer creation

* Async loading of react-vtkjs-viewport

* withCornerstone HOC to wrap routes requiring CornerstoneWADOImageLoader initiazation

* removed dead code in comments

* initWeWorkers is no more. Remove al its references.

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-04-02 13:31:14 -04:00
Pierre-Antoine Ganaye e09a3d2e93 docs: Remove dead links in docs and improve SOP Class Handler docs (#1593)
* fix docs dead links

* add examples to SOP handler docs
2020-04-02 09:24:49 -04:00
ohif-bot 978a86bf12 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.5.1
 - @ohif/extension-dicom-html@1.1.2
 - @ohif/extension-dicom-microscopy@0.50.9
 - @ohif/extension-dicom-pdf@1.0.3
 - @ohif/viewer@3.8.14
2020-04-02 07:01:58 +00:00
0139a32213 chore: Switch asyncComponent to React Lazy/Suspense (Closes #1577) (#1582)
* Attempted Lazy load for OHIFCornerStoneViewport

* Converted dicom-(html, microscopy, pdf) & cornerstone extensions to React Lazy components

* Fixed incorrect import from OHIFDicomPDFSopClassHandler to ConnectedOHIFDicomPDFViewer

Co-authored-by: Ankit Mohan <ankit.mohan@live.in>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2020-04-02 08:59:17 +02:00
ohif-bot d57158b59d chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.1.1
 - @ohif/extension-vtk@1.3.6
 - @ohif/core@2.6.5
 - @ohif/viewer@3.8.13
2020-04-01 03:55:23 +00:00
Igor Octaviano 4a7ce1c093 fix: segmentation not loading (#1566)
* Fix bug

* Refactor the state of the component RTPanel
2020-03-31 23:54:02 -04:00
ohif-bot 333677c40e chore(release): publish [skip ci]
- @ohif/extension-dicom-microscopy@0.50.8
 - @ohif/viewer@3.8.12
2020-03-31 13:30:26 +00:00
James Petts b459b894bd Sm fix (#1585)
* Fix broken SM viewport.

* fix: 🐛 Fix broken SM viewport
2020-03-31 15:28:11 +02:00
Andrey Fedorov c5a4860209 update stale.yml to not close IDC related issues (#1573) 2020-03-29 16:10:11 -04:00
ohif-bot 4f79bda7c9 chore(release): publish [skip ci]
- @ohif/viewer@3.8.11
2020-03-26 03:28:31 +00:00
7d22bb7d5a fix: #1312 Cine dialog remains on screen (#1540)
Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-03-25 23:25:04 -04:00
ohif-bot 36822cc6ce chore(release): publish [skip ci]
- @ohif/viewer@3.8.10
2020-03-26 01:47:51 +00:00
Mirna Silva e676f0b5b5 test: E2E tests for W/L Presets (#1557)
* E2E tests for W/L Presets

* CI Fix
2020-03-25 21:46:28 -04:00
ohif-bot 57de66a65f chore(release): publish [skip ci]
- @ohif/viewer@3.8.9
2020-03-25 17:55:57 +00:00
Kedarandkedar.netelixir 99022f2bac fix: Load measurement in active viewport. (#1558)
Possible solution for request https://github.com/OHIF/Viewers/issues/1522 ?

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-25 13:53:47 -04:00
ohif-bot 47b1e2837f chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.5
 - @ohif/core@2.6.4
 - @ohif/viewer@3.8.8
2020-03-25 17:04:29 +00:00
Kedarandkedar.netelixir d604ebaffd fix: disable autoFreeze of immer, even in dev mode (#1560)
Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-25 12:56:46 -04:00
Erik ZieglerandDanny Brown c1a0d3c662 fix: Add support for single entries in SequenceOfUltrasoundRegions. M… (#1559)
* fix: Add support for single entries in SequenceOfUltrasoundRegions. More than one region will need lower-level changes

* Update platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-03-25 12:55:39 -04:00
ohif-bot 16866a47a0 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.4
 - @ohif/core@2.6.3
 - @ohif/viewer@3.8.7
2020-03-24 17:13:15 +00:00
Erik Ziegler 77ab0ad9a1 fix: Ensure we take into account pixel spacing fields properly (#1555)
* fix: Ensure we take into account pixel spacing fields properly

* Rename to getPixelSpacingInformation
2020-03-24 18:10:58 +01:00
294 changed files with 14466 additions and 4385 deletions

No files matched your search

+6
View File
@@ -28,6 +28,12 @@ if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
fi
if [ -n "${PORT}" ]
then
echo "Changing port to ${PORT}..."
sed -i -e "s/listen 80/listen ${PORT}/g" /etc/nginx/conf.d/default.conf
fi
echo "Starting Nginx to serve the OHIF Viewer..."
exec "$@"
+3
View File
@@ -18,6 +18,9 @@ exemptLabels:
- 'Triage :white_flag:'
- 'Extension: Discussion'
- 'Announcement 🎉'
- 'IDC:priority'
- 'IDC:candidate'
- 'IDC:collaboration'
# Label to use when marking an issue as stale
staleLabel: 'Stale :baguette_bread:'
# Comment to post when marking an issue as stale. Set to `false` to disable
+14
View File
@@ -11,6 +11,7 @@ node -v
# Install build deps and all monorepo package dependencies. Yarn Workspaces
# should also symlink all projects appropriately
yarn run lerna:restore
yarn install --no-ignore-optional --pure-lockfile
# Build && Move PWA Output
@@ -21,6 +22,19 @@ mv platform/viewer/dist/* .netlify/www/pwa -v
# Build && Move script output
# 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.'
# Build using react-scripts
+1 -1
View File
@@ -122,7 +122,7 @@ window.config = {
```
- Install the viewer:
`window.OHIFStandaloneViewer.installViewer(window.config);`
`window.OHIFViewer.installViewer(window.config);`
This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
+1 -1
View File
@@ -48,7 +48,7 @@ Check out these helpful links:
-->
<!-- prettier-ignore-start -->
[ohif-org]: https://www.ohif.org
[ohif-org]: http://www.ohif.org
[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb
[gh-issues]: https://github.com/OHIF/Viewers/issues
<!-- prettier-ignore-end -->
+1
View File
@@ -5,6 +5,7 @@
- [Getting Started](development/getting-started.md)
- [Contributing](development/contributing.md)
- [Continuous Integration](development/continous-integration.md)
- [Testing](development/testing.md)
- [Configuring](configuring/index.md)
- [Data Source](configuring/data-source.md)
+5 -10
View File
@@ -13,7 +13,6 @@ architected, and the role each of it's dependent libraries plays.
- [Overview](#overview)
- [Business Logic](#business-logic)
- [Component Library](#react-component-library)
- [Internationalization](#internationalization-i18n)
- [Extensions & Configuration](#extensions--configuration)
- [Common Questions](#common-questions)
@@ -74,10 +73,10 @@ This diagram is a conceptual illustration of how the Viewer is architected.
The [`@ohif/core`][core-github] project offers pre-packaged solutions for
features common to Web-based medical imaging viewers. For example:
- [Hotkeys](#)
- Hotkeys
- DICOM Web requests
- Hanging Protocols
- [Managing a study's measurements](../services/default/measurements.md)
- Managing a study's measurements
- Managing a study's DICOM metadata
- [A flexible pattern for extensions](../extensions/index.md)
- And many others
@@ -99,10 +98,6 @@ application.
[Check out our component library!](https://react.ohif.org/)
## Internationalization (i18n)
...
## Extensions & Configuration
While OHIF maintains several high value and commonly requested features in its
@@ -120,7 +115,7 @@ the viewer. Some common use cases include:
We expose common integration points via [extensions](../extensions/index.md) to
make this possible. The viewer and many of our own extensions also offer
[configuration][configuration]. For a list of extensions maintained by OHIF,
[check out this helpful table](../extensions/index.mdl#maintained-extensions).
[check out this helpful table](../extensions/index.md#maintained-extensions).
If you find yourself thinking "I wish the Viewer could do X", and you can't
accomplish it with an extension today, create a GitHub issue! We're actively
@@ -147,12 +142,12 @@ you would need to recreate.
-->
<!-- prettier-ignore-start -->
[monorepo]: #
[monorepo]: https://github.com/OHIF/Viewers/issues/768
[viewers-project]: https://github.com/OHIF/Viewers
[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer
[pwa]: https://developers.google.com/web/progressive-web-apps/
[configuration]: ../configuring/index.md
[extensions]: ../extensions/index.md
[core-github]: https://github.com/OHIF/viewers/platform/core
[ui-github]: https://github.com/OHIF/Viewers/platform/ui
[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui
<!-- prettier-ignore-end -->
+107 -10
View File
@@ -123,14 +123,111 @@ There are two important steps to making sure this setup works:
Most image archives do not provide either of these features "out of the box".
It's common to use IIS, Nginx, or Apache to route incoming requests and append
appropriate headers. You can find an example of this setup in our
[Nginx + Image Archive Deployment Recipe](deployment/recipes/nginx--image-archive.md).
[Nginx + Image Archive Deployment Recipe](./recipes/nginx--image-archive.md).
#### What if my archive doesn't support DicomWeb?
> This is possible to do with the OHIF Viewer, but not as straightforward. Look
> out for documentation on this subject in the near future.
It's possible to supply all Study data via JSON format, in the event you do not have a DicomWeb endpoint.
You can host all of the relevant files on any web accessible server (Amazon S3, Azure Blob Storage, Local file server etc.)
This JSON is supplied via the '?url=' query parameter.
It should reference an endpoint that returns **application/json** formatted text.
If you do not have an API, you can simply return a text file containing the JSON from any web server.
You tell the OHIF viewer to use JSON by appending the `'?url='` query to the `/Viewer` route:
eg. `https://my-test-ohif-server/viewer?url=https://my-json-server/study-uid.json`
The returned JSON object must contain a single root object with a 'studies' array.
*Sample JSON format:*
```JSON
{
"studies": [
{
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
"StudyDescription": "BRAIN SELLA",
"StudyDate": "20010108",
"StudyTime": "120022",
"PatientName": "MISTER^MR",
"PatientId": "832040",
"series": [
{
"SeriesDescription": "SAG T-1",
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
"SeriesNumber": 2,
"SeriesDate": "20010108",
"SeriesTime": "120318",
"Modality": "MR",
"instances": [
{
"metadata": {
"Columns": 512,
"Rows": 512,
"InstanceNumber": 3,
"AcquisitionNumber": 0,
"PhotometricInterpretation": "MONOCHROME2",
"BitsAllocated": 16,
"BitsStored": 16,
"PixelRepresentation": 1,
"SamplesPerPixel": 1,
"PixelSpacing": [0.390625, 0.390625],
"HighBit": 15,
"ImageOrientationPatient": [0,1,0,0,0,-1],
"ImagePositionPatient": [11.600000,-92.500000, 98.099998],
"FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470",
"ImageType": ["ORIGINAL","PRIMARY","OTHER"],
"Modality": "MR",
"SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124",
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78"
},
"url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm"
}
]
}
]
}
]
}
```
More info on this JSON format can be found here [Issue #1500](https://github.com/OHIF/Viewers/issues/1500)
**Implementation Notes:**
1. When hosting the viewer, you will also need to host a /viewer route on the server - or the browser may not be able to find the route.
2. For each instance url (dicom object) in the returned JSON, you must prefix the `url` with `dicomweb:` in order for the cornerstone image loader to retrieve it correctly.
eg. `https://image-server/my-image.dcm` ---> `dicomweb:https://image-server/my-image.dcm`
3. The JSON format above is compatible with >= v3.7.8 of the application. Older versions of the viewer used a different JSON format. As of 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does not support this format yet.
4. The JSON format is case-sensitive. Please ensure you have matched casing with the naturalised Dicom format referenced in [Issue #1500](https://github.com/OHIF/Viewers/issues/1500).
*CORS Issues (Cross-Origin Resource Sharing)*
If you host a JSON API or Images on a different domain from the the app itself, you will likely have CORS issues. This will also happen when testing from Localhost and reaching out to remote servers.
Even if the domain is the same, different ports, subdomains or protocols (https vs http) will also cause CORS errors.
You will to need add a configuration on each server hosting these assets to allow your App server origin.
For example:
Lets assume your application is hosted on `https://my-ohif-server.com`.
Your JSON API is hosted on `https://my-json-api.aws.com`
And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com`
When you first start your application, browsing to `https://my-ohif-server.com/viewer?url=https://my-json-api.aws.com/api/my-json-study-info.json`, you will likely get a CORS error in the browser console as it tries to connect to `https://my-json-api.aws.com`.
Adding a setting on the JSON server to allow the CORS origin = `https://my-ohif-server.com` should solve this.
Next, you will likely get a similar CORS error, as the browser tries to go to `https://my-s3-bucket.aws.com`.
You will need to go to the S3 bucket configuration, and add a CORS setting to allow origin = `https://my-ohif-server.com`.
Essentially, whenever the application connects to a remote resource, you will need to add the applications url to the allowed CORS Origins on that resource. Adding an origin similar to https://localhost:3000 will also allow for local testing.
...
### Securing Your Data
@@ -166,7 +263,7 @@ window.config = {
```
You can find an example of this setup in our
[User Account Control Deployment Recipe](deployment/recipes/user-account-control.md).
[User Account Control Deployment Recipe](./recipes/user-account-control.md).
#### Choosing a Flow for the Viewer
@@ -183,12 +280,12 @@ many possible configurations, so please don't feel limited to these setups.
Please feel free to suggest or contribute your own recipes.
- Script Include
- [Embedding the Viewer](deployment/recipes/embedded-viewer.md)
- [Embedding the Viewer](./recipes/embedded-viewer.md)
- Stand-Alone
- [Build for Production](deployment/recipes/build-for-production.md)
- [Static](deployment/recipes/static-assets.md)
- [Nginx + Image Archive](deployment/recipes/nginx--image-archive.md)
- [User Account Control](deployment/recipes/user-account-control.md)
- [Build for Production](./recipes/build-for-production.md)
- [Static](./recipes/static-assets.md)
- [Nginx + Image Archive](./recipes/nginx--image-archive.md)
- [User Account Control](./recipes/user-account-control.md)
<!--
Links
@@ -50,7 +50,7 @@ window.config = {
```
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](./index.md).
[Configuration Guide](../../configuring/index.md).
<ol start="3"><li>
Render the viewer in the web page's target <code>div</code>
@@ -126,12 +126,39 @@ globally defined styles/scripts.
> We're trying to embed the OHIF Viewer into an existing React App, but seeing
> react-dom and react conflicts. What can we do?
If you are installing OHIF viewer inside another react app, you may use `installViewer` as follows:
```
import { installViewer } from '@ohif/viewer'
const ohifViewerConfig = window.config // or set it here
const containerId = 'ohif'
const componentRenderedOrUpdatedCallback = function() {
console.log('OHIF Viewer rendered/updated');
};
componentDidMount() {
installViewer(
ohifViewerConfig,
containerId,
componentRenderedOrUpdatedCallback
);
}
render () {
...
//you can render in any element you wish
<AnyTag id={containerId}/>
}
```
`installViewer` is a convenience method that pulls in some dependencies that may
not be compatible with existing `react` apps. `@ohif/viewer` also exports `App`
which is a react component that takes the `configuration` outlined above as
props. You can use it as a reusable component, and to avoid `react` version
conflict issues.
<!--
LINKS
-->
@@ -49,9 +49,7 @@ We can solve this one of two ways:
1. Have our Image Archive located at the same domain as our Web App
2. Add appropriate `Access-Control-Allow-*` HTTP headers
This solution uses the first approach, but you can see an example of the second
in the `docker-compose` bundled with this project for local development:
[HERE](https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/Nginx-Orthanc/docker-compose.yml)
**This solution uses the first approach.**
You can read more about CORS in this Medium article: [Understanding
CORS][understanding-cors]
@@ -121,7 +119,10 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc.js`
@@ -138,11 +139,11 @@ Viewer's configuration, you can run:
All other files are found in: `/docker/OpenResty-Orthanc/`
| Service | Configuration | Docs |
| ----------------- | ---------------------------------------------- | ------------------------------------------- |
| OHIF Viewer | [dockerfile][dockerfile] / [config.js][config] | You're reading them now! |
| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] |
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
| Service | Configuration | Docs |
| ----------------- | --------------------------------- | ------------------------------------------- |
| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! |
| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] |
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
## Next Steps
@@ -218,10 +219,11 @@ following resources helpful:
- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/)
- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/)
For a different take on this setup, check out the repository one of our
community members put together:
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
@@ -234,8 +236,7 @@ community members put together:
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[config]: #
[dockerfile]: #
[config-nginx]: #
[config-orthanc]: #
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json
<!-- prettier-ignore-end -->
@@ -122,7 +122,10 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js`
@@ -146,8 +149,8 @@ All other files are found in: `/docker/OpenResty-Orthanc-Keycloak/`
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
| Keycloak | [`/ohif-keycloak-realm.json`][config-keycloak]\* | |
- \* These are the seed values for Keycloak. They can be manually updated at
`http://127.0.0.1/auth/admin`
\* These are the seed values for Keycloak. They can be manually updated at
`http://127.0.0.1/auth/admin`
#### Keycloak Themeing
@@ -261,12 +264,12 @@ for OAuth:
- [Diagrams of OpenID Connect Flows](https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660)
- [KeyCloak: OpenID Connect Flows](https://www.keycloak.org/docs/latest/securing_apps/index.html#authorization-code)
- [Good description on SSO Protocols](https://www.keycloak.org/docs/2.5/server_admin/topics/sso-protocols/oidc.html)
For a different take on this setup, check out the repository one of our
community members put together:
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
@@ -278,8 +281,8 @@ community members put together:
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/src/config.js
[dockerfile]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-nginx]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-orthanc]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-keycloak]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json
[config-keycloak]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json
<!-- prettier-ignore-end -->
+2 -2
View File
@@ -114,8 +114,8 @@ the tips above will help ensure your changes are reviewed.
## Testing contribution pull requests
OHIF uses [netlify](netlify.com) so that pull requests are autogenerated and
available for testing.
OHIF uses [netlify](https://www.netlify.com/) so that pull requests are
autogenerated and available for testing.
For example, [this url][example-url] allows you to test [pull request 237, the
request that created this FAQ entry,][pr-237] using data pulled from Amazon S3.
+1 -1
View File
@@ -132,7 +132,7 @@ feature `X` or scenario `Y`? Open an issue and let's discuss.
[flow-org]: https://flow.org/
<!-- Talks -->
[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw
[kent-talk]: https://www.youtube.com/watch?list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf
[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE
[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk
[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests
<!-- Images -->
@@ -31,6 +31,7 @@
</td>
<td>Viewport, Toolbar</td>
</tr>
<!-- dicom-html -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-html">DICOM HTML</a>
@@ -40,6 +41,7 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-pdf -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-pdf">DICOM PDF</a>
@@ -49,6 +51,7 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-microscopy -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-microscopy">DICOM Microscopy</a>
@@ -58,5 +61,15 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-segmentation -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-segmentation">DICOM Segmentation</a>
</td>
<td>
Renders segmentation images for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7">specific SopClassUID</a>.
</td>
<td>Panel, Toolbar</td>
</tr>
</tbody>
</table>
+11 -9
View File
@@ -74,7 +74,8 @@ export default {
### Registering an Extension
There are two different ways to register and configure extensions: At
[runtime](#runtime-extensions) and at [build time](#bundled-extensions).
[runtime](#registering-at-runtime) and at
[build time](#registering-at-build-time).
You can leverage one or both strategies. Which one(s) you choose depend on your
application's requirements. Each [module](#modules) defined by the extension
@@ -82,9 +83,9 @@ becomes available to the core application via the `ExtensionManager`.
#### Registering at Runtime
The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for
that file includes an `Extensions` key that supports an array of extensions to
register.
The `@ohif/viewer` uses a [configuration file](../viewer/configuration.md) at
startup. The schema for that file includes an `Extensions` key that supports an
array of extensions to register.
```js
// prettier-ignore
@@ -193,8 +194,8 @@ today, create a GitHub issue!
The `ExtensionManager` is a class made available to us via the `@ohif/core`
project (platform/core). Our application instantiates a single instance of it,
and provides a `ServicesManager` and `CommandsManager` along with the application's
configuration through the appConfig key (optional).
and provides a `ServicesManager` and `CommandsManager` along with the
application's configuration through the appConfig key (optional).
```js
const commandsManager = new CommandsManager();
@@ -202,7 +203,7 @@ const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
appConfig
appConfig,
});
```
@@ -212,8 +213,9 @@ The `ExtensionManager` only has a few public members:
- `registerExtensions` - Registers an array of extensions
- `modules` - An object containing registered extensions by `MODULE_TYPE`
During registration, lifecycle hooks and modules have access to the extension's config,
the application's config and `ExtensionManager`'s `ServicesManager` and `CommandsManager` instances.
During registration, lifecycle hooks and modules have access to the extension's
config, the application's config and `ExtensionManager`'s `ServicesManager` and
`CommandsManager` instances.
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
appropriate places in our application.
@@ -1,9 +1,9 @@
# Module: SOP Class Handler
An extension can register a SOP Class Handler Module by defining a
`getSopClassHandlerModule` method. The SOP Class Handler is a bit different from
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
own components. It instead defines:
An extension can register a [SOP Class][sop-class-link] Handler Module by
defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]
Handler is a bit different from the other modules, as it doesn't provide a `1:1`
schema for UI or provide it's own components. It instead defines:
- `sopClassUIDs`: an array of string SOP Class UIDs that the
`getDisplaySetFromSeries` method should be applied to.
@@ -76,6 +76,13 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) {
};
```
### More examples :
- [Dicom-HTML SOP][dicom-html-sop]
- [Dicom-PDF SOP][dicom-pdf-sop]
- [Dicom-Microscopy SOP][dicom-micro-sop]
- [Dicom-Segmentation SOP][dicom-seg-sop]
## `@ohif/viewer` usage
We use the `sopClassHandlerModule`s in three different places:
@@ -88,3 +95,11 @@ Each time, it is used to map study and series data to `displaySets`. It does
this by working alongside the `StudyMetadataManager` in `@ohif/core`. That
manager has the method `createDisplaySets` that takes an array of
`sopClassHandlerModules`.
<!-- prettier-ignore-start -->
[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html
[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12
[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6
[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7
[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7
<!-- prettier-ignore-end -->
+2 -2
View File
@@ -75,7 +75,7 @@ items to our ["Active Development" GitHub Project Board][gh-board]. Items on
this project board are either in active development by Core Team members, or
queued up for development as in-progress items are completed.
> :pencil: Want to contribute but not sure where to start? Check out [Up for
> 🖋 Want to contribute but not sure where to start? Check out [Up for
> grabs][label-grabs] issues and our [Contributing
> documentation][contributing-docs]
@@ -156,5 +156,5 @@ addressed and a Stable version is released.
[ohif-prod]: https://viewer.ohif.org
[gh-board]: https://github.com/OHIF/Viewers/projects/4
[label-grabs]: https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs+%3Araising_hand_woman%3A%22
[contributing-docs]: https://docs.ohif.org/contributing/
[contributing-docs]: ./development/contributing.md
<!-- prettier-ignore-end -->
+1 -1
View File
@@ -62,7 +62,7 @@ able to fail or be removed. Related patterns that may reduce coupling include:
-->
<!-- prettier-ignore-start -->
[commands]: #/
[commands]: ../extensions/modules/commands.md
[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services
[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js
[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern
+2 -2
View File
@@ -2,8 +2,8 @@
- [Overview](#overview)
- [Example](#example)
- [Tips & Tricks](#/)
- [Maintained Services](#/)
- [Tips & Tricks](#tips--tricks)
- [Maintained Services](#maintained-services)
## Overview
+53 -1
View File
@@ -11,7 +11,59 @@ are:
```js
window.config = {
routerBasename: '/',
whiteLabelling: {},
/**
* "White Labeling" is used to change the branding, look, and feel of the OHIF
* Viewer. These settings, and the color variables that are used by our components,
* are the easiest way to rebrand the application.
*
* More extensive changes are made possible through swapping out the UI library,
* Viewer project, or extensions.
*/
whiteLabeling: {
/* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
createLogoComponentFn: function(React) {
return React.createElement('a', {
target: '_self',
rel: 'noopener noreferrer',
className: 'header-brand',
href: '/',
style: {
display: 'block',
textIndent: '-9999px',
background: 'url(/svg-file-hosted-at-domain-root.svg)',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
width: '200px',
},
});
},
},
/**
* Internally, the OHIF Viewer fetches data primarily with the
* `cornerstoneWADOImageLoader` and the `DICOMWebClient`. If either of these
* receive a non-200 response, this method allows you to handle that error.
*
* Common use cases include:
* - Showing a notification with the UINotificationService
* - Redirecting the user
* - Refreshing an auth token
*
* @param {Object} error - JS new Error()
* @param {XMLHttpRequest} error.request - The XHR request that's onreadystate change triggered this callback
* @param {string} error.response - The XHR's response property
* @param {number} error.status - The XHR's status property
*/
httpErrorHandler: error => {
const { request: xhr, response, status } = err;
const { responseType, statusText } = xhr;
// In local files, status is 0 upon success in Firefox
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log(statusText, response, responseType);
} else {
console.warn('Likely CORS error');
}
},
extensions: [],
showStudyList: true,
filterQueryParam: false,
+4 -4
View File
@@ -135,7 +135,7 @@ console.log(T('$t(Common:Play) my translated text'));
# Main Concepts While Translating
## - Namespaces
## Namespaces
Namespaces are being used to organize translations in smaller portions, combined
semantically or by use. Each `.json` file inside `@ohif/i18n` npm package
@@ -157,7 +157,7 @@ NameSpace, like this following example getting data from `Common` NameSpace:
$t(Common:Reset)
```
## - Extending Languages in @ohif/i18n
## Extending Languages in @ohif/i18n
Sometimes, even using the same language, some nouns or jargons can change
according to the country, states or even from Hospital to Hospital.
@@ -212,7 +212,7 @@ object like this:
Please check the `index.js` files inside locales folder for an example of this
exporting structure.
### - Extending languages dynamically
### Extending languages dynamically
You have access to the i18next instance, so you can use the
[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init)
@@ -314,4 +314,4 @@ REACT_APP_I18N_DEBUG=true yarn run dev
### Contributing with new languages
Contributions of any kind are welcome! Please check the
[instructions](https://docs.ohif.org/contributing.html).
[instructions](../development/contributing.md).
+4 -4
View File
@@ -66,11 +66,11 @@ open to pull requests and discussion issues.
Current white-labeling options are limited. We expose the ability to replace the
"Logo" section of the application with a custom "Logo" component. You can do
this by adding a `whiteLabelling` key to your
this by adding a `whiteLabeling` key to your
[configuration file](./configuration.md).
```js
function RadicalImagingLogo() {
function RadicalImagingLogo(React) {
return React.createElement(
'a',
{
@@ -83,8 +83,8 @@ function RadicalImagingLogo() {
);
}
props.whiteLabelling = {
logoComponent: RadicalImagingLogo(),
props.whiteLabeling = {
createLogoComponentFn: RadicalImagingLogo,
};
```
+17 -12
View File
@@ -20,10 +20,10 @@ abbrev@~1.0.9:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU=
agent-base@4, agent-base@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"
@@ -773,9 +773,9 @@ err-code@^1.0.0:
prr "~1.0.1"
es6-promise@^4.0.3:
version "4.2.6"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f"
integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
es6-promisify@^5.0.0:
version "5.0.0"
@@ -1289,11 +1289,11 @@ http-signature@~1.2.0:
sshpk "^1.7.0"
https-proxy-agent@^2.1.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.1.0"
agent-base "^4.3.0"
debug "^3.1.0"
humanize-ms@^1.2.1:
@@ -1807,11 +1807,16 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.0.0, ms@^2.1.1:
ms@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
mute-stream@~0.0.4:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
+196
View File
@@ -3,6 +3,202 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.4...@ohif/extension-cornerstone@2.9.5) (2020-09-29)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.3...@ohif/extension-cornerstone@2.9.4) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.2...@ohif/extension-cornerstone@2.9.3) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [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)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [2.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.1...@ohif/extension-cornerstone@2.7.2) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.0...@ohif/extension-cornerstone@2.7.1) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24)
### Features
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.0...@ohif/extension-cornerstone@2.6.1) (2020-04-23)
### Bug Fixes
* 🐛 Multiframe fix ([#1661](https://github.com/OHIF/Viewers/issues/1661)) ([7120561](https://github.com/OHIF/Viewers/commit/71205618ecb8b592247c5acb32284bfe7e18fce5))
# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.2...@ohif/extension-cornerstone@2.6.0) (2020-04-23)
### Features
* configuration to hook into XHR Error handling ([e96205d](https://github.com/OHIF/Viewers/commit/e96205de35e5bec14dc8a9a8509db3dd4e6ecdb6))
## [2.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.1...@ohif/extension-cornerstone@2.5.2) (2020-04-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.0...@ohif/extension-cornerstone@2.5.1) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.3...@ohif/extension-cornerstone@2.5.0) (2020-03-13)
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.5.0",
"version": "2.9.5",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -34,9 +34,9 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.10.1",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.3",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
@@ -51,6 +51,6 @@
"classnames": "^2.2.6",
"lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1",
"react-cornerstone-viewport": "^2.3.6"
"react-cornerstone-viewport": "4.0.4"
}
}
@@ -179,7 +179,7 @@ class OHIFCornerstoneViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@@ -215,12 +215,28 @@ class OHIFCornerstoneViewport extends Component {
});
}
const newImageHandler = ({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
};
return (
<>
<ConnectedCornerstoneViewport
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
onNewImage={newImageHandler}
onNewImageDebounceTime={700}
// ~~ Connected (From REDUX)
// frameRate={frameRate}
// isPlaying={false}
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+41 -4
View File
@@ -7,6 +7,17 @@ import { getEnabledElement } from './state';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
const scroll = cornerstoneTools.import('util/scroll');
const { studyMetadataManager } = OHIF.utils;
const { setViewportSpecificData } = OHIF.redux.actions;
const refreshCornerstoneViewports = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
});
};
const commandsModule = ({ servicesManager }) => {
const actions = {
rotateViewport: ({ viewports, rotation }) => {
@@ -177,10 +188,7 @@ const commandsModule = ({ servicesManager }) => {
measurementApi.syncMeasurementsAndToolData();
// Update images in all active viewports
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
refreshCornerstoneViewports();
},
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
const nearbyTool = {};
@@ -255,9 +263,38 @@ const commandsModule = ({ servicesManager }) => {
cornerstone.setViewport(enabledElement, viewport);
}
},
jumpToImage: ({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex,
}) => {
const study = studyMetadataManager.get(StudyInstanceUID);
const displaySet = study.findDisplaySet(ds => {
return (
ds.images &&
ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
);
});
displaySet.SOPInstanceUID = SOPInstanceUID;
displaySet.frameIndex = frameIndex;
window.store.dispatch(
setViewportSpecificData(activeViewportIndex, displaySet)
);
refreshCornerstoneViewports();
},
};
const definitions = {
jumpToImage: {
commandFn: actions.jumpToImage,
storeContexts: [],
options: {},
},
getNearbyToolData: {
commandFn: actions.getNearbyToolData,
storeContexts: [],
+25 -8
View File
@@ -1,14 +1,21 @@
import React from 'react';
import init from './init.js';
import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
import { version } from '../package.json';
const OHIFCornerstoneViewport = asyncComponent(() =>
import(
/* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'
)
);
const Component = React.lazy(() => {
return import('./OHIFCornerstoneViewport');
});
const OHIFCornerstoneViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
/**
*
@@ -18,6 +25,7 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'cornerstone',
version,
/**
*
@@ -28,8 +36,17 @@ export default {
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });
},
getViewportModule() {
return OHIFCornerstoneViewport;
getViewportModule({ commandsManager }) {
const ExtendedOHIFCornerstoneViewport = props => {
const onNewImageHandler = jumpData => {
commandsManager.runCommand('jumpToImage', jumpData);
};
return (
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
);
};
return ExtendedOHIFCornerstoneViewport;
},
getToolbarModule() {
return toolbarModule;
@@ -2,6 +2,9 @@ import Hammer from 'hammerjs';
import cornerstone from 'cornerstone-core';
import cornerstoneMath from 'cornerstone-math';
import cornerstoneTools from 'cornerstone-tools';
import OHIF from '@ohif/core';
const { log } = OHIF;
export default function(configuration = {}) {
// For debugging
@@ -12,6 +15,13 @@ export default function(configuration = {}) {
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
cornerstoneTools.init(configuration);
cornerstoneTools.loadHandlerManager.setErrorLoadingHandler(
(element, imageId, error) => {
log.error(imageId);
throw error;
}
);
// Set the tool font and font size
// context.font = "[style] [variant] [weight] [size]/[line height] [font family]";
const fontFamily =
@@ -24,4 +34,11 @@ export default function(configuration = {}) {
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
cornerstoneTools.store.state.touchProximity = 40;
// Configure stack prefetch
cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity,
preserveExistingPool: false,
maxSimultaneousRequests: 20,
});
}
@@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};
@@ -0,0 +1,38 @@
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDicomP10Downloader',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
});
};
+68
View File
@@ -0,0 +1,68 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.4...@ohif/extension-debugging@0.1.5) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-debugging
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.3...@ohif/extension-debugging@0.1.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-debugging
## 0.1.3 (2020-09-03)
**Note:** Version bump only for package @ohif/extension-debugging
## [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)
### Features
* configuration to hook into XHR Error handling ([e96205d](https://github.com/OHIF/Viewers/commit/e96205de35e5bec14dc8a9a8509db3dd4e6ecdb6))
## 0.0.2 (2020-04-15)
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
# Change Log
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
# @ohif/extension-dicom-p10-downloader
+41
View File
@@ -0,0 +1,41 @@
{
"name": "@ohif/extension-debugging",
"version": "0.1.5",
"description": "OHIF extension for debugging.",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"prepublishOnly": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^2.6.0",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.5.2"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"detect-browser": "5.1.1",
"dicomweb-client": "^0.6.0",
"file-saver": "^2.0.2",
"jszip": "^3.2.2"
}
}
@@ -0,0 +1,9 @@
.debugReportModalHeader {
padding: 10px 0 10px;
color: var(--active-color);
}
.debug-report-modal-container {
display: flex;
flex-direction: column;
}
@@ -0,0 +1,245 @@
import React from 'react';
import { detect } from 'detect-browser';
import './DebugReportModal.css';
import { ToolbarButton } from '@ohif/ui';
const DubugReportModal = ({
viewports,
studies,
servers,
extensionManager,
mailTo,
}) => {
const mailToFunction = () => {
const StudyInstanceUID = Object.keys(studies.studyData)[0];
const subject = encodeURI(`Issue with Study: ${StudyInstanceUID}`);
let body = `Enter the description of your problem here: \n\n\n`;
body += `============= SESSION INFO =============\n\n`;
// App version
body += '== App ==\n';
body += `version\t${window.version}\n\n`;
// Extensions Versions
body += '== Extensions Versions ==\n';
const { registeredExtensionVesions } = extensionManager;
Object.keys(registeredExtensionVesions).forEach(extensionId => {
const version = registeredExtensionVesions[extensionId];
body += `${extensionId}\t${version}\n`;
});
body += '\n';
// Browser Info
const browser = detect();
const { name, os, type, version } = browser;
body += '== Browser Info ==\n';
body += `name\t ${name}\n`;
body += `os\t ${os}\n`;
body += `type\t ${type}\n`;
body += `version\t ${version}\n\n`;
// Study URL
body += '== URL ==\n';
body += `URL\t ${window.location.href}\n\n`;
// Layout
const { numRows, numColumns, viewportSpecificData } = viewports;
body += '== Viewport Layout ==\n';
body += `Rows\t${numRows}\n`;
body += `Columns\t${numColumns}\n\n`;
body += '== Viewports ==\n';
Object.keys(viewportSpecificData).forEach(viewportIndex => {
const vsd = viewportSpecificData[viewportIndex];
const [row, column] = _viewportIndexToViewportPosition(
viewportIndex,
numColumns
);
body += `[${row},${column}]\t${vsd.SeriesInstanceUID}\n`;
});
// TODO Text dump of rest of stuff.
body = encodeURI(body);
window.location.href = `mailto:${mailTo}?subject=${subject}&body=${body}`;
};
return (
<div className="debug-report-modal-container">
{mailTo ? (
<div>
<ToolbarButton
label={'Send Bug Report'}
onClick={mailToFunction}
icon={'envelope-square'}
isActive={false}
/>
</div>
) : null}
<div>
<table>
{getAppVersion()}
{getExtensionVersions(extensionManager)}
{getBrowserInfo()}
{getCurrentStudyUrl()}
{getLayout(viewports)}
</table>
</div>
</div>
);
};
const getAppVersion = () => {
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">App</th>
</tr>
<tr>
<td>Version</td>
<td>{window.version}</td>
</tr>
</React.Fragment>
);
};
const getCurrentStudyUrl = () => {
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">URL</th>
</tr>
<tr>
<td>URL</td>
<td>{window.location.href}</td>
</tr>
</React.Fragment>
);
};
const getExtensionVersions = extensionManager => {
const { registeredExtensionVesions } = extensionManager;
const lineItems = Object.keys(registeredExtensionVesions).map(extensionId => {
const version = registeredExtensionVesions[extensionId];
return (
<tr>
<td>{extensionId}</td>
<td>{version}</td>
</tr>
);
});
return (
<React.Fragment>
<th className="debugReportModalHeader">Extensions</th>
{lineItems}
</React.Fragment>
);
};
const getLayout = viewports => {
const { numRows, numColumns } = viewports;
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">Viewports</th>
</tr>
<tr>
<th>Layout</th>
</tr>
<tr>
<td>Rows</td>
<td>{numRows}</td>
</tr>
<tr>
<td>Columns</td>
<td>{numColumns}</td>
</tr>
<tr>
<th>SeriesInstanceUIDs</th>
</tr>
{getSeriesInstanceUIDsPerRow(viewports)}
</React.Fragment>
);
};
const getBrowserInfo = () => {
const browser = detect();
const { name, os, type, version } = browser;
return (
<React.Fragment>
<tr>
<th className="debugReportModalHeader">Platform</th>
</tr>
<tr>
<td>name</td>
<td>{name}</td>
</tr>
<tr>
<td>os</td>
<td>{os}</td>
</tr>
<tr>
<td>type</td>
<td>{type}</td>
</tr>
<tr>
<td>version</td>
<td>{version}</td>
</tr>
</React.Fragment>
);
};
const getSeriesInstanceUIDsPerRow = viewports => {
const { viewportSpecificData, numColumns } = viewports;
// NOTE viewportSpecificData is actually an object with numerical keys.
return Object.keys(viewportSpecificData).map(viewportIndex => {
const vsd = viewportSpecificData[viewportIndex];
const [row, column] = _viewportIndexToViewportPosition(
viewportIndex,
numColumns
);
return (
<tr>
<td>{`[${row},${column}]`}</td>
<td>{vsd.SeriesInstanceUID}</td>
</tr>
);
});
};
const _viewportIndexToViewportPosition = (viewportIndex, numColumns) => {
const row = Math.floor(viewportIndex / numColumns);
const column = viewportIndex % numColumns;
return [row, column];
};
export default DubugReportModal;
+173
View File
@@ -0,0 +1,173 @@
import OHIF from '@ohif/core';
import {
save,
upload,
getDicomWebClientFromContext,
getStudyInstanceUIDFromStudies,
getSOPInstanceReferenceFromActiveViewport,
getSOPInstanceReferencesFromViewports,
} from './utils';
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
import DebugReportModal from './DebugReportModal';
import React from 'react';
import state from './state';
const {
utils: { Queue },
} = OHIF;
export function getCommands(context, servicesManager, extensionManager) {
const queue = new Queue(1);
const actions = {
/**
* @example Running this command using Commands Manager
* commandsManager.runCommand(
* 'downloadAndZip',
* {
* listOfUIDs: [...],
* options: {
* progress(status) {
* console.info('Progress:', (status.progress * 100).toFixed(2) + '%');
* }
* }
* },
* 'VIEWER'
* );
*/
downloadAndZip({ servers, dicomWebClient, listOfUIDs, options }) {
return save(
_downloadAndZip(
dicomWebClient || getDicomWebClientFromContext(context, servers),
listOfUIDs,
options
),
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 }) {
const dicomWebClient = getDicomWebClientFromContext(context, servers);
const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports);
return save(
_downloadAndZip(dicomWebClient, listOfUIDs, { progress }),
listOfUIDs
);
},
downloadAndZipSeriesOnActiveViewport({ servers, viewports, progress }) {
const dicomWebClient = getDicomWebClientFromContext(context, servers);
const listOfUIDs = getSOPInstanceReferenceFromActiveViewport(viewports);
return save(
_downloadAndZip(dicomWebClient, listOfUIDs, { progress }),
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
);
},
openDebugInfoModal({ viewports, studies, servers }) {
const { UIModalService } = servicesManager.services;
const WrappedDebugReportModal = function() {
return (
<DebugReportModal
viewports={viewports}
studies={studies}
servers={servers}
extensionManager={extensionManager}
mailTo={state.mailTo}
/>
);
};
UIModalService.show({
content: WrappedDebugReportModal,
title: `Debugging Information`,
});
},
};
const definitions = {
openDebugInfoModal: {
commandFn: actions.openDebugInfoModal,
storeContexts: ['viewports', 'servers', 'studies'],
},
downloadAndZip: {
commandFn: queue.bindSafe(actions.downloadAndZip, error),
storeContexts: ['servers'],
},
downloadAndZipStudy: {
commandFn: queue.bindSafe(actions.downloadAndZipStudy, error),
storeContexts: ['servers', 'studies'],
options: { progress },
},
downloadAndZipSeriesOnViewports: {
commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error),
storeContexts: ['servers', 'viewports'],
options: { progress },
},
downloadAndZipSeriesOnActiveViewport: {
commandFn: queue.bindSafe(
actions.downloadAndZipSeriesOnActiveViewport,
error
),
storeContexts: ['servers', 'viewports'],
options: { progress },
},
downloadAndUploadStudy: {
commandFn: queue.bindSafe(actions.downloadAndUploadStudy, error),
storeContexts: ['servers', 'studies'],
options: { progress },
},
};
return {
actions,
definitions,
};
}
/**
* Utils
*/
function progress(status) {
OHIF.log.info(
'Download and Zip Progress:',
(status.progress * 100.0).toFixed(2) + '%'
);
}
function error(e) {
if (e.message === 'Queue limit reached') {
OHIF.log.warn('A download is already in progress, please wait.');
} else {
OHIF.log.error(e);
}
}
+259
View File
@@ -0,0 +1,259 @@
import OHIF from '@ohif/core';
import { api } from 'dicomweb-client';
import dicomParser from 'dicom-parser';
import JSZip from 'jszip';
/**
* Constants
*/
const {
utils: {
isDicomUid,
hierarchicalListUtils,
progressTrackingUtils: progressUtils,
},
} = OHIF;
/**
* Public Methods
*/
/**
* Download and Zip all DICOM P10 instances from specified DICOM Web Client
* based on an hierarchical list of UIDs;
*
* @param {DICOMwebClient} dicomWebClient A DICOMwebClient instance through
* which the referenced instances will be retrieved;
* @param {Array} listOfUIDs The hierarchical list of UIDs from the instances
* that should be retrieved:
* A hierarchical list of UIDs is a regular JS Array where the type of the UID
* (study, series, instance) is determined by its nasting lavel. For example:
* @ The following list instructs the library to download all the instances
* from both studies "A" and "B":
*
* ['studyUIDFromA', 'studyUIDFromB']
*
* @ In the previous example both UIDs are treated as STUDY UIDs because both
* of them are listed in the same (top) level of the list. If, on the other
* hand, only instances from series "I" and "J" from the study "B"
* are to be downloaded, the expected hierarchical list would be:
*
* ['studyUIDFromA', ['studyUIDFromB', ['seriesUIDFromI', 'seriesUIDFromJ']]]
*
* @ Which, when prettified, reads like this:
*
* [
* 'studyUIDFromA',
* ['studyUIDFromB', [
* 'seriesUIDFromI',
* 'seriesUIDFromJ'
* ]]
* ]
*
* @ Furthermore, if only instances "X", "Y" and "Z" from series "J" need to
* be downloaded (instead of all the instances from that series), the list
* could be changed to:
*
* [
* 'studyUIDFromA',
* ['studyUIDFromB', [
* 'seriesUIDFromI',
* ['seriesUIDFromJ', [
* 'instanceUIDFromX',
* 'instanceUIDFromY',
* 'instanceUIDFromZ'
* ]]
* ]]
* ]
*
* Please refer to hierarchicalListUtils.js for more information and utilities;
*
* @param {Object} options A plain object with options;
* @param {function} options.progress A callback to retrieve notifications
* @returns {Promise} A promise that resolves to an URL from which the ZIP file
* can be downloaded;
*/
async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
if (dicomWebClient instanceof api.DICOMwebClient) {
const settings = buildSettings(listOfUIDs, options);
const { compression } = settings.tasks;
const buffers = await downloadBuffers(settings, dicomWebClient);
compression.deferred.resolve(zipAll(buffers, settings));
const url = await compression.deferred.promise;
return url;
}
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
*/
async function zipAll(buffers, settings) {
const zip = new JSZip();
OHIF.log.info('Adding DICOM P10 files to archive:', buffers.length);
buffers.forEach((buffer, i) => {
const path = buildPath(buffer) || `${i}.dcm`;
zip.file(path, buffer);
});
// Set compression task progress to 50%
progressUtils.update(settings.tasks.compression.task, 0.5);
const blob = await zip.generateAsync({ type: 'blob' });
return URL.createObjectURL(blob);
}
function buildSettings(listOfUIDs, options) {
const taskList = progressUtils.createList();
const compression = progressUtils.addDeferred(taskList);
const downloads = [];
// Build downloads list
hierarchicalListUtils.forEach(
listOfUIDs,
(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) => {
if (isDicomUid(StudyInstanceUID)) {
downloads.push({
tracking: progressUtils.addDeferred(taskList),
parameters: [StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID],
});
}
}
);
// Print tree of hierarchical references
OHIF.log.info('Downloading DICOM P10 files for references:');
OHIF.log.info(hierarchicalListUtils.print(listOfUIDs));
return {
options: Object(options),
taskList,
tasks: {
downloads,
compression,
},
};
}
function buildPath(buffer) {
let path;
try {
const byteArray = new Uint8Array(buffer);
const dataSet = dicomParser.parseDicom(byteArray, {
// Stop parsing after SeriesInstanceUID is found
untilTag: 'x0020000e',
});
const StudyInstanceUID = dataSet.string('x0020000d');
const SeriesInstanceUID = dataSet.string('x0020000e');
const SOPInstanceUID = dataSet.string('x00080018');
if (StudyInstanceUID && SeriesInstanceUID && SOPInstanceUID) {
path = `${StudyInstanceUID}/${SeriesInstanceUID}/${SOPInstanceUID}.dcm`;
}
} catch (e) {
OHIF.log.error('Error parsing downloaded DICOM P10 file...', e);
}
return path;
}
async function downloadAll(dicomWebClient, settings) {
const { downloads } = settings.tasks;
// Make sure at least one download was initiated
if (downloads.length < 1) {
throw new Error('No valid reference to be downloaded');
}
const promises = downloads.map(item => {
const {
parameters,
tracking: { deferred, task },
} = item;
deferred.resolve(download(task, dicomWebClient, ...parameters));
return deferred.promise;
});
// Wait on created download promises
return Promise.all(promises).then(results => {
const buffers = [];
// The "results" array may directly contain buffers (ArrayBuffer instances)
// or arrays of buffers, depending on the type of downloads initiated on the
// previous step (retrieveStudy, retrieveSeries or retrieveinstance). Ex:
// results = [buf1, [buf2, buf3], buf4, [buf5], ...];
results.forEach(
function select(nesting, result) {
if (result instanceof ArrayBuffer) {
buffers.push(result);
} else if (nesting && Array.isArray(result)) {
// "nesting" argument is important to make sure only two levels
// of arrays are visited. For example, "bufX" should not be visited:
// [buf1, [buf2, buf3, [bufX]], buf4, [buf5], ...];
result.forEach(select.bind(null, false));
}
}.bind(null, true)
);
return buffers;
});
}
async function download(
task,
dicomWebClient,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
) {
// Strict DICOM-formatted variable names COULDN'T be used here because the
// DICOM Web client interface expects them in this specific format.
// @TODO: Add support for download progress handler which will use the
// currently not use "task" param
if (!isDicomUid(studyInstanceUID)) {
throw new Error('Download requires at least a "StudyInstanceUID" property');
}
if (!isDicomUid(seriesInstanceUID)) {
// Download entire study
return dicomWebClient.retrieveStudy({
studyInstanceUID,
});
}
if (!isDicomUid(sopInstanceUID)) {
// Download entire series
return dicomWebClient.retrieveSeries({
studyInstanceUID,
seriesInstanceUID,
});
}
// Download specific instance
return dicomWebClient.retrieveInstance({
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
});
}
/**
* Exports
*/
export { downloadAndZip as default, downloadAndZip, downloadInstances };
+55
View File
@@ -0,0 +1,55 @@
import { getDicomWebClientFromConfig } from './utils';
import { getCommands } from './commandsModule';
import { version } from '../package.json';
import toolbarModule from './toolbarModule';
import state from './state';
/**
* Constants
*/
/**
* Globals
*/
const sharedContext = {
dicomWebClient: null,
};
/**
* Extension
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'dicom-p10-downloader',
version,
/**
* LIFECYCLE HOOKS
*/
preRegistration({ appConfig, configuration }) {
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
if (dicomWebClient) {
sharedContext.dicomWebClient = dicomWebClient;
}
if (configuration && configuration.mailTo) {
state.mailTo = configuration.mailTo;
}
},
/**
* MODULE GETTERS
*/
getCommandsModule({ servicesManager, extensionManager }) {
return getCommands(sharedContext, servicesManager, extensionManager);
},
getToolbarModule() {
return toolbarModule;
},
};
+3
View File
@@ -0,0 +1,3 @@
const state = { mailTo: undefined };
export default state;
+20
View File
@@ -0,0 +1,20 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
};
const definitions = [
{
id: 'Debug Info',
label: 'Debug Info',
icon: 'cog',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'openDebugInfoModal',
context: 'VIEWER',
},
];
export default {
definitions,
defaultContext: 'VIEWER',
};
+158
View File
@@ -0,0 +1,158 @@
import OHIF from '@ohif/core';
import { api } from 'dicomweb-client';
import { saveAs } from 'file-saver';
const {
utils: { isDicomUid, resolveObjectPath, hierarchicalListUtils },
DICOMWeb,
} = OHIF;
function validDicomUid(subject) {
if (isDicomUid(subject)) {
return subject;
}
}
function getActiveServerFromServersStore(store) {
const servers = resolveObjectPath(store, 'servers');
if (Array.isArray(servers) && servers.length > 0) {
return servers.find(server => resolveObjectPath(server, 'active') === true);
}
}
function getDicomWebClientFromConfig(config) {
const servers = resolveObjectPath(config, 'servers.dicomWeb');
if (Array.isArray(servers) && servers.length > 0) {
const server = servers[0];
return new api.DICOMwebClient({
url: server.wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(server),
});
}
}
function getDicomWebClientFromContext(context, store) {
const activeServer = getActiveServerFromServersStore(store);
if (activeServer) {
return new api.DICOMwebClient({
url: activeServer.wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(activeServer),
});
} else if (context.dicomWebClient instanceof api.DICOMwebClient) {
return context.dicomWebClient;
}
}
function getSOPInstanceReference(viewports, index) {
if (index >= 0) {
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = Object(
resolveObjectPath(viewports, `viewportSpecificData.${index}`)
);
return Object.freeze(
hierarchicalListUtils.addToList(
[],
validDicomUid(StudyInstanceUID),
validDicomUid(SeriesInstanceUID),
validDicomUid(SOPInstanceUID)
)
);
}
}
function getSOPInstanceReferenceFromActiveViewport(viewports) {
return getSOPInstanceReference(
viewports,
resolveObjectPath(viewports, 'activeViewportIndex')
);
}
function getSOPInstanceReferencesFromViewports(viewports) {
const list = [];
const viewportSpecificData = resolveObjectPath(
viewports,
'viewportSpecificData'
);
Object.keys(viewportSpecificData).forEach(index => {
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = Object(
viewportSpecificData[index]
);
hierarchicalListUtils.addToList(
list,
validDicomUid(StudyInstanceUID),
validDicomUid(SeriesInstanceUID),
validDicomUid(SOPInstanceUID)
);
});
return list;
}
function save(promise, listOfUIDs) {
return Promise.resolve(promise)
.then(url => {
OHIF.log.info('Files successfully compressed:', url);
const StudyInstanceUID = hierarchicalListUtils.getItem(listOfUIDs, 0);
saveAs(url, `${StudyInstanceUID}.zip`);
return url;
})
.catch(error => {
OHIF.log.error('Failed to create Zip file...', error);
return null;
});
}
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 {
save,
upload,
validDicomUid,
getDicomWebClientFromConfig,
getDicomWebClientFromContext,
getStudyInstanceUIDFromStudies,
getSOPInstanceReferenceFromActiveViewport,
getSOPInstanceReferencesFromViewports,
};
+89
View File
@@ -3,6 +3,95 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.7...@ohif/extension-dicom-html@1.2.8) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.6...@ohif/extension-dicom-html@1.2.7) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.5...@ohif/extension-dicom-html@1.2.6) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [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)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.0...@ohif/extension-dicom-html@1.2.1) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-html
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.2...@ohif/extension-dicom-html@1.2.0) (2020-04-24)
### Features
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.1...@ohif/extension-dicom-html@1.1.2) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.0...@ohif/extension-dicom-html@1.1.1) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "1.1.1",
"version": "1.2.8",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"dcmjs": "^0.10.1",
"dcmjs": "0.16.3",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as dcmjs from 'dcmjs';
import dcmjs from 'dcmjs';
import TypedArrayProp from './TypedArrayProp';
import './DicomHtmlViewport.css';
@@ -22,6 +22,14 @@ const OHIFDicomHtmlSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDescription,
SeriesNumber,
SeriesDate,
SeriesTime,
} = metadata;
return {
plugin: 'html',
Modality: 'SR',
@@ -31,6 +39,11 @@ const OHIFDicomHtmlSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
metadata,
SeriesDate,
SeriesTime,
SeriesNumber,
authorizationHeaders,
};
},
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+14 -6
View File
@@ -1,17 +1,25 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
import { version } from '../package.json';
const OHIFDicomHtmlViewport = asyncComponent(() =>
import(
/* webpackChunkName: "OHIFDicomHtmlViewport" */ './OHIFDicomHtmlViewport.js'
)
);
const Component = React.lazy(() => {
return import('./OHIFDicomHtmlViewport');
});
const OHIFDicomHtmlViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'html',
version,
getViewportModule() {
return OHIFDicomHtmlViewport;
+51
View File
@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.1...@ohif/extension-dicom-microscopy@0.51.2) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [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)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.7...@ohif/extension-dicom-microscopy@0.50.8) (2020-03-31)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.6...@ohif/extension-dicom-microscopy@0.50.7) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.50.7",
"version": "0.51.3",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -12,6 +12,14 @@ const DicomMicroscopySopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDescription,
SeriesNumber,
ContentDate,
ContentTime,
} = metadata;
// Note: We are passing the dicomweb client into each viewport!
return {
@@ -22,6 +30,11 @@ const DicomMicroscopySopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
metadata,
};
},
};
@@ -34,11 +34,12 @@ class DicomMicroscopyViewport extends Component {
.then(instances => {
const promises = [];
for (let i = 0; i < instances.length; i++) {
const sopInstanceUid = instances[i]['00080018']['Value'][0];
const sopInstanceUID = instances[i]['00080018']['Value'][0];
const retrieveInstanceOptions = {
studyInstanceUID: displaySet.StudyInstanceUID,
seriesInstanceUID: displaySet.SeriesInstanceUID,
sopInstanceUid,
sopInstanceUID,
};
const promise = dicomWebClient
@@ -91,8 +92,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2>
) : (
<div style={style} ref={this.container} />
)}
<div style={style} ref={this.container} />
)}
</div>
);
}
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+14 -6
View File
@@ -1,17 +1,25 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
import { version } from '../package.json';
const DicomMicroscopyViewport = asyncComponent(() =>
import(
/* webpackChunkName: "DicomMicroscopyViewport" */ './DicomMicroscopyViewport.js'
)
);
const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
});
const DicomMicroscopyViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'microscopy',
version,
getViewportModule() {
return DicomMicroscopyViewport;
+32
View File
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.4...@ohif/extension-dicom-pdf@1.0.5) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [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)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.1...@ohif/extension-dicom-pdf@1.0.2) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "1.0.2",
"version": "1.0.6",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@@ -12,6 +12,14 @@ const OHIFDicomPDFSopClassHandler = {
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
ContentDate,
ContentTime,
SeriesDescription,
SeriesNumber,
} = metadata;
return {
plugin: 'pdf',
Modality: 'DOC',
@@ -21,6 +29,11 @@ const OHIFDicomPDFSopClassHandler = {
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
SeriesDescription,
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
SeriesTime: ContentTime,
SeriesNumber,
metadata,
authorizationHeaders: authorizationHeaders,
};
},
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+14 -6
View File
@@ -1,17 +1,25 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
import { version } from '../package.json';
const ConnectedOHIFDicomPDFViewer = asyncComponent(() =>
import(
/* webpackChunkName: "ConnectedOHIFDicomPDFViewer" */ './ConnectedOHIFDicomPDFViewer'
)
);
const Component = React.lazy(() => {
return import('./ConnectedOHIFDicomPDFViewer');
});
const ConnectedOHIFDicomPDFViewer = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'pdf',
version,
getViewportModule() {
return ConnectedOHIFDicomPDFViewer;
},
@@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};
@@ -0,0 +1,44 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDicomSeg',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};
+206
View File
@@ -0,0 +1,206 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.5...@ohif/extension-dicom-rt@0.4.6) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.4...@ohif/extension-dicom-rt@0.4.5) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.3...@ohif/extension-dicom-rt@0.4.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.2...@ohif/extension-dicom-rt@0.4.3) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.1...@ohif/extension-dicom-rt@0.4.2) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [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)
### Bug Fixes
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
## [0.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.2...@ohif/extension-dicom-rt@0.1.3) (2020-05-12)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.1...@ohif/extension-dicom-rt@0.1.2) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.0...@ohif/extension-dicom-rt@0.1.1) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-rt
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.0.2...@ohif/extension-dicom-rt@0.1.0) (2020-04-24)
### Features
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
## 0.0.2 (2020-04-09)
**Note:** Version bump only for package @ohif/extension-dicom-rt
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
# @ohif/extension-dicom-rt
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@ohif/extension-dicom-rt",
"version": "0.4.6",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:dicom-html": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"prepublishOnly": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.3",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-tooltip": "4.2.10"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"react-select": "^3.0.8"
}
}
@@ -0,0 +1,246 @@
import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core';
import loadRTStruct from './loadRTStruct';
import getSourceDisplaySet from './getSourceDisplaySet';
import id from './id';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_RT_STRUCT: '1.2.840.10008.5.1.4.1.1.481.3',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
const OHIFDicomRTStructSopClassHandler = {
id: 'OHIFDicomRTStructSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = metadata;
// TODO -> GET REFERENCED FRAME OF REFERENCE SEQUENCE.
const rtStructDisplaySet = {
Modality: 'RTSTRUCT',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
metadata,
};
if (!metadata.ReferencedSeriesSequence) {
const ReferencedFrameOfReferenceSequence =
metadata.ReferencedFrameOfReferenceSequence;
if (ReferencedFrameOfReferenceSequence) {
// TODO -> @dannyrb Do we augment metadata or add a (potentially large? fallback list in filterDerivedDisplaySets )
metadata.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
ReferencedFrameOfReferenceSequence
);
}
}
rtStructDisplaySet.getSourceDisplaySet = function(studies) {
return getSourceDisplaySet(studies, rtStructDisplaySet);
};
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
return loadRTStruct(
rtStructDisplaySet,
referencedDisplaySet,
studies
).catch(error => {
rtStructDisplaySet.isLoaded = false;
rtStructDisplaySet.loadError = true;
throw new Error(error);
});
};
return rtStructDisplaySet;
},
};
function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
ReferencedFrameOfReferenceSequence
) {
const ReferencedSeriesSequence = [];
_getSequenceAsArray(ReferencedFrameOfReferenceSequence).forEach(
referencedFrameOfReference => {
const { RTReferencedStudySequence } = referencedFrameOfReference;
_getSequenceAsArray(RTReferencedStudySequence).forEach(
rtReferencedStudy => {
const { RTReferencedSeriesSequence } = rtReferencedStudy;
_getSequenceAsArray(RTReferencedSeriesSequence).forEach(
rtReferencedSeries => {
const ReferencedInstanceSequence = [];
const {
ContourImageSequence,
SeriesInstanceUID,
} = rtReferencedSeries;
_getSequenceAsArray(ContourImageSequence).forEach(
contourImage => {
ReferencedInstanceSequence.push({
ReferencedSOPInstanceUID:
contourImage.ReferencedSOPInstanceUID,
ReferencedSOPClassUID: contourImage.ReferencedSOPClassUID,
});
}
);
const referencedSeries = {
SeriesInstanceUID,
ReferencedInstanceSequence,
};
ReferencedSeriesSequence.push(referencedSeries);
}
);
}
);
}
);
return ReferencedSeriesSequence;
}
function _getSequenceAsArray(sequence) {
return Array.isArray(sequence) ? sequence : [sequence];
}
function _getReferencedFrameOfReferenceSequence(instance) {
const referencedFrameOfReferenceSequenceRaw = instance['30060010'];
const referencedFrameOfReferenceSequence = [];
if (
!referencedFrameOfReferenceSequenceRaw ||
!referencedFrameOfReferenceSequenceRaw.Value
) {
return undefined;
}
referencedFrameOfReferenceSequenceRaw.Value.forEach(
referencedFrameOfReferenceRaw => {
const frameOfReferenceUID = DICOMWeb.getString(
referencedFrameOfReferenceRaw['00200052']
);
const referencedFrameOfReference = { frameOfReferenceUID };
const RTReferencedStudySequenceRaw =
referencedFrameOfReferenceRaw['30060012'];
referencedFrameOfReferenceSequence.push(referencedFrameOfReference);
if (RTReferencedStudySequenceRaw && RTReferencedStudySequenceRaw.Value) {
referencedFrameOfReference.rtReferencedStudySequence = [];
const { rtReferencedStudySequence } = referencedFrameOfReference;
RTReferencedStudySequenceRaw.Value.forEach(rtReferencedStudyRaw => {
const referencedSopClassUID = DICOMWeb.getString(
rtReferencedStudyRaw['00081150']
);
const referencedSOPInstanceUID = DICOMWeb.getString(
rtReferencedStudyRaw['00081155']
);
const rtReferencedStudy = {
referencedSopClassUID,
referencedSOPInstanceUID,
rtReferencedSeriesSequence: [],
};
rtReferencedStudySequence.push(rtReferencedStudy);
const { rtReferencedSeriesSequence } = rtReferencedStudy;
const rtReferencedSeriesSequenceRaw =
rtReferencedStudyRaw['30060014'];
rtReferencedSeriesSequenceRaw.Value.forEach(rtReferencedSeriesRaw => {
const seriesInstanceUID = DICOMWeb.getString(
rtReferencedSeriesRaw['0020000E']
);
const rtReferencedSeries = {
seriesInstanceUID,
contourImageSequence: [],
};
rtReferencedSeriesSequence.push(rtReferencedSeries);
const { contourImageSequence } = rtReferencedSeries;
const contourImageSequenceRaw = rtReferencedSeriesRaw['30060016'];
contourImageSequenceRaw.Value.forEach(contourImageRaw => {
const referencedSOPClassUID = DICOMWeb.getString(
contourImageRaw['00081150']
);
const referencedSOPInstanceUID = DICOMWeb.getString(
contourImageRaw['00081155']
);
const referencedFrameNumber = DICOMWeb.getString(
contourImageRaw['00081160']
);
const referencedSegmentNumber = DICOMWeb.getString(
contourImageRaw['0062000B']
);
const contourImage = {
referencedSOPClassUID,
referencedSOPInstanceUID,
referencedFrameNumber,
referencedSegmentNumber,
};
contourImageSequence.push(contourImage);
});
});
});
}
}
);
return referencedFrameOfReferenceSequence;
}
export default OHIFDicomRTStructSopClassHandler;
@@ -0,0 +1,61 @@
import React from 'react';
const LoadingIndicator = ({ height = "200px", width = "200px", expand = false }) => {
return (
<div style={expand ? { height: "100%", width: "100%" } : {}}>
<svg style={{ margin: 'auto', background: 'none', display: 'block', shapeRendering: 'auto' }} width={width} height={height} viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g transform="rotate(0 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(30 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(60 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(90 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(120 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(150 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(180 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(210 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(240 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(270 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(300 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(330 50 50)">
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
</rect>
</g>
</svg>
</div>
);
};
export default LoadingIndicator;
@@ -0,0 +1,44 @@
.dcmrt-panel-section {
display: flex;
flex-direction: column;
color: var(--text-primary-color);
font-size: 12px;
}
.dcmrt-panel-section .header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: var(--ui-gray-dark);
border-radius: 5px;
font-weight: bold;
padding: 10px 15px;
}
.dcmrt-panel-section .header .eye-icon,
.dcmrt-panel-section .header .angle-double-down,
.dcmrt-panel-section .header .angle-double-up {
cursor: pointer;
color: var(--active-color);
}
.dcmrt-panel-section .header .eye-icon:hover,
.dcmrt-panel-section .header .angle-double-down:hover,
.dcmrt-panel-section .header .angle-double-up:hover {
color: var(--hover-color);
}
.dcmrt-panel-section .header .eye-icon.expanded {
color: var(--default-color);
}
.icons {
display: flex;
align-items: center;
border-radius: 5px;
}
.icons .angle-double-down, .icons .angle-double-up {
margin-left: 15px;
}
@@ -0,0 +1,82 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Icon } from '@ohif/ui';
import './PanelSection.css';
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
const PanelSection = ({
title,
children,
visible = false,
expanded = false,
loading = false,
hideVisibleButton = false,
onVisibilityChange = () => { },
onExpandChange = () => { }
}) => {
const [isExpanded, setIsExpanded] = useState(expanded);
const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
setIsVisible(visible);
}, [visible]);
return (
<div
className="dcmrt-panel-section"
style={{
marginBottom: isExpanded ? 0 : 2,
height: isExpanded ? '100%' : 'unset'
}}
>
<div className="header">
<div>{title}</div>
<div className="icons">
{!hideVisibleButton && (
<Icon
className={`eye-icon ${isVisible && 'expanded'}`}
name={isVisible ? "eye" : "eye-closed"}
width="20px"
height="20px"
onClick={() => {
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
}}
/>
)}
<Icon
className={`angle-double-${isExpanded ? 'down' : 'up'} ${isExpanded && 'expanded'}`}
name={`angle-double-${isExpanded ? 'down' : 'up'}`}
width="20px"
height="20px"
onClick={() => {
const newExpandValue = !isExpanded;
setIsExpanded(newExpandValue);
onExpandChange(newExpandValue);
}}
/>
</div>
</div>
{loading && isExpanded && <LoadingIndicator expand height="70px" width="70px" />}
{children}
</div>
);
};
PanelSection.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
visible: PropTypes.bool,
expanded: PropTypes.bool,
onVisibilityChange: PropTypes.func
};
PanelSection.defaultProps = {
visible: false,
expanded: false,
onVisibilityChange: () => { }
};
export default PanelSection;
@@ -0,0 +1,26 @@
.dcmrt-panel {
display: flex;
flex-direction: column;
background-color: var(--primary-background-color);
height: 100%;
width: 100%;
padding: 20px;
}
.dcmrt-panel .dcmrt-panel-header {
display: flex;
flex-direction: row;
justify-content: space-between;
color: white;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon {
align-self: flex-end;
color: var(--default-color);
cursor: pointer;
margin-bottom: 20px;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon:hover {
color: var(--hover-color);
}
@@ -0,0 +1,335 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import { utils } from '@ohif/core';
import './RTPanel.css';
import StructureSetItem from '../StructureSetItem/StructureSetItem';
import RTPanelSettings from '../RTSettings/RTSettings';
import PanelSection from '../PanelSection/PanelSection';
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
import TOOL_NAMES from '../../utils/toolNames';
const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;
const { studyMetadataManager } = utils;
const refreshViewport = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
});
};
/**
* RTPanel component
*
* @param {Object} props
* @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData
* @param {number} props.activeIndex - activeViewportIndex
* @param {number} props.isOpen - isOpen
* @returns component
*/
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 DEFAULT_SET_INDEX = 0;
const DEFAULT_STATE = {
referencedDisplaysets: [],
sets: [],
selectedSet: null,
};
const [state, setState] = useState(DEFAULT_STATE);
const [showSettings, setShowSettings] = useState(false);
const activeViewport = viewports[activeIndex];
/*
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the rtstruct panel
* or other components that could rely on loaded rtstructs that
* the first batch of structs were loaded so that e.g. when the user opens the panel
* before the structs are fully loaded, the panel can subscribe to this custom event
* and update itself with the new structs.
*
* This limitation is due to the fact that the rtmodule is an object (which will be
* updated after the structs are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the rtstruct loading process in any other component
* without subscribing to external events.
*/
useEffect(() => {
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => {
document.removeEventListener(
'extensiondicomrtrtloaded',
updateStructureSets
);
};
}, []);
const updateStructureSets = () => {
const module = cornerstoneTools.getModule('rtstruct');
const StructureSets = module.state.StructureSets;
if (StructureSets && StructureSets.length) {
const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
if (viewportSets.length) {
const defaultSet = viewportSets[DEFAULT_SET_INDEX];
setState({
referencedDisplaysets,
selectedSet: defaultSet,
sets: viewportSets,
});
} else {
setState(DEFAULT_STATE);
}
}
};
useEffect(() => {
updateStructureSets();
}, [studies, viewports, activeIndex]);
useEffect(() => {
setShowSettings(showSettings && !isOpen);
}, [isOpen]);
const toContourItem = (
{ ROINumber, ROIName, RTROIObservations, colorArray, visible, isSupported },
loadedSet
) => {
let interpretedType = '';
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
}
const isSameContour = selectedContour && selectedContour === ROINumber;
return (
<StructureSetItem
key={ROINumber}
selected={isSameContour}
isDisabled={!isSupported}
onClick={() => {
setSelectedContour(isSameContour ? null : ROINumber);
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const stackToolState = cornerstoneTools.getToolState(
element,
'stack'
);
if (!stackToolState) {
return;
}
const imageIds = stackToolState.data[0].imageIds;
const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber,
imageIds
);
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
const imageIdSpecificToolState = toolState[imageId];
const rtstructData =
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];
const specificData = rtstructData.data.find(
rtData => rtData.ROINumber === ROINumber
);
specificData.highlight = true;
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}`}
index={ROINumber}
color={colorArray}
visible={visible}
onVisibilityChange={() => {
const module = cornerstoneTools.getModule('rtstruct');
module.setters.toggleROIContour(
state.selectedSet.SeriesInstanceUID,
ROINumber
);
}}
/>
);
};
const configurationChangeHandler = newConfiguration => {
const module = cornerstoneTools.getModule('rtstruct');
module.configuration.lineWidth = newConfiguration.lineWidth;
module.configuration.opacity = newConfiguration.opacity;
refreshViewport();
};
if (showSettings) {
const module = cornerstoneTools.getModule('rtstruct');
return (
<RTPanelSettings
configuration={module.configuration}
onBack={() => setShowSettings(false)}
onChange={configurationChangeHandler}
/>
);
}
return (
<div className="dcmrt-panel">
<div className="dcmrt-panel-header">
<h3>RT Structure Sets</h3>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSettings(true)}
/>
</div>
{!state.referencedDisplaysets.length && (
<LoadingIndicator expand height="70px" width="70px" />
)}
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
);
const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
return (
<PanelSection
key={SeriesInstanceUID}
title={metadata.StructureSetLabel}
loading={!isLoaded || !loadedSet}
visible={isLoaded && loadedSet && loadedSet.visible}
hideVisibleButton={!isLoaded}
expanded={
isLoaded &&
loadedSet.SeriesInstanceUID ===
state.selectedSet.SeriesInstanceUID
}
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
if (newVisibility) {
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
} else {
module.setters.hideStructureSet(loadedSet.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 }));
}
}}
>
<ScrollableArea>
<TableList headless>
{isLoaded &&
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
</div>
);
};
RTPanel.propTypes = {
viewports: PropTypes.shape({
displaySetInstanceUID: PropTypes.string,
frameRate: PropTypes.any,
InstanceNumber: PropTypes.number,
isMultiFrame: PropTypes.bool,
isReconstructable: PropTypes.bool,
Modality: PropTypes.string,
plugin: PropTypes.string,
SeriesDate: PropTypes.string,
SeriesDescription: PropTypes.string,
SeriesInstanceUID: PropTypes.string,
SeriesNumber: PropTypes.any,
SeriesTime: PropTypes.string,
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
StudyInstanceUID: PropTypes.string,
}),
activeIndex: PropTypes.number.isRequired,
studies: PropTypes.array.isRequired,
isOpen: PropTypes.bool.isRequired,
};
RTPanel.defaultProps = {};
export default RTPanel;
@@ -0,0 +1,88 @@
.dcmrt-settings {
display: flex;
flex-direction: column;
padding: 20px;
color: white;
}
.dcmrt-settings .settings-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding-bottom: 20px;
}
.dcmrt-settings .settings-title h3 {
color: var(--text-primary-color);
margin: 0;
padding: 0;
}
.dcmrt-settings .settings-section .content {
padding: 10px;
background-color: var(--ui-gray-dark);
border-radius: 5px;
}
.dcmrt-settings .settings-section .header {
border-bottom: 2px solid black;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: var(--ui-gray-dark);
border-radius: 5px;
font-weight: bold;
font-size: 12px;
padding: 10px 15px;
}
.dcmrt-settings .settings-section p {
font-weight: bold;
}
.dcmrt-settings .range {
display: flex;
padding: 10px 10px;
font-size: 12px;
}
.dcmrt-settings .range:not(:last-of-type) {
padding-bottom: 0;
}
.dcmrt-settings .range label {
margin: 0;
display: unset;
min-width: 40px;
max-width: 40px;
}
.dcmrt-settings .range input {
width: 100%;
padding: 0 20px 0 20px;
}
.dcmrt-settings .return-button {
color: #333;
background-color: #fff;
padding: 4px 12px;
line-height: 1.4;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid #ccc;
border-radius: 4px;
transition: all 0.15s ease;
}
.dcmrt-settings .return-button:hover,
.dcmrt-settings .return-button:active,
.dcmrt-settings .return-button.active {
background-color: var(--active-color);
border-color: var(--ui-border-color-active);
color: #fff;
}
@@ -0,0 +1,71 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Range } from '@ohif/ui';
import './RTSettings.css';
const RTSettings = ({ configuration, onBack, onChange }) => {
const toFloat = value => parseFloat(value / 100).toFixed(2);
const save = (field, value) => {
onChange({ ...configuration, [field]: value });
};
const SettingsSection = ({ title, children }) => {
return (
<div className="settings-section">
<div className="header">{title}</div>
<div className="content">
{children}
</div>
</div>
);
};
return (
<div className="dcmrt-settings">
<div className="settings-title">
<h3>RT Structure Set Settings</h3>
<button className="return-button" onClick={onBack}>
Back
</button>
</div>
<SettingsSection title="Segment Outline">
<div className="range">
<label htmlFor="range">Opacity</label>
<Range
showPercentage
step={1}
min={0}
max={100}
value={configuration.opacity * 100}
onChange={event => save('opacity', toFloat(event.target.value))}
/>
</div>
<div className="range">
<label htmlFor="range">Width</label>
<Range
showValue
step={1}
min={1}
max={5}
value={configuration.lineWidth}
onChange={event => save('lineWidth', parseInt(event.target.value))}
/>
</div>
</SettingsSection>
</div>
);
};
RTSettings.propTypes = {
configuration: PropTypes.shape({
lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
}).isRequired,
onBack: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
export default RTSettings;
@@ -0,0 +1,93 @@
.dcmrt-structure-set-item .item-color-section {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.dcmrt-structure-set-item .item-color {
margin-top: 8px;
border-radius: 100%;
width: 20px;
height: 20px;
}
.dcmrt-structure-set-item .item-info {
display: inline-block;
margin-top: 9px;
padding-left: 9px;
width: 90px;
border-left: 1px solid var(--text-secondary-color);
color: var(--text-primary-color);
width: 100%;
}
.dcmrt-structure-set-item .item-actions {
margin-left: -1px;
background-color: var(--ui-gray-darker);
height: 0;
overflow: hidden;
transition: all 0.3s ease;
visibility: hidden;
padding-left: 14px;
}
.dcmrt-structure-set-item .item-actions .btnAction {
background-color: transparent;
border: none;
color: var(--default-color);
cursor: pointer;
line-height: 35px;
height: 35px;
transition: all 0.3s ease;
}
.dcmrt-structure-set-item .item-actions .btnAction:hover,
.dcmrt-structure-set-item .item-actions .btnAction:active {
color: var(--text-primary-color);
}
.dcmrt-structure-set-item .item-actions .btnAction i {
margin-right: 4px;
}
.dcmrt-structure-set-item.selected .item-actions {
height: 35px;
visibility: visible;
}
.dcmrt-structure-set-item .item-label {
display: flex;
justify-content: space-between;
padding-right: 10px;
}
.dcmrt-structure-set-item .item-label span {
overflow-wrap: normal;
white-space: nowrap;
overflow: hidden;
max-width: calc(100% - 40px); /* calc(100% - 50px); 20px = eye icon */
text-overflow: ellipsis;
}
.dcmrt-structure-set-item .item-label .eye-icon {
cursor: pointer;
color: var(--active-color);
}
.dcmrt-structure-set-item .item-label .eye-icon:hover {
color: var(--hover-color);
}
.dcmrt-structure-set-item .item-label .eye-icon.--visible {
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);
}
@@ -0,0 +1,175 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
import ReactTooltip from 'react-tooltip';
import './StructureSetItem.css';
const ColoredCircle = ({ color }) => {
return (
<div
className="item-color"
style={{ backgroundColor: `rgba(${color.join(',')})` }}
></div>
);
};
ColoredCircle.propTypes = {
color: PropTypes.array.isRequired,
};
const StructureSetItem = ({
index,
label,
isDisabled,
onClick,
itemClass,
color,
visible = true,
onVisibilityChange,
selected = false,
}) => {
const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
setIsVisible(visible);
}, [visible]);
let dcmrtClassNames = `dcmrt-structure-set-item`;
if (selected) {
dcmrtClassNames += ' selected';
}
if (isDisabled) {
dcmrtClassNames += ' isDisabled';
}
const warningIcon = (
<span>
<Icon name="exclamation-triangle" />
</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 }}>
<a data-tip data-for={`StructureHover${index}`}>
<span>{label}</span>
</a>
<ReactTooltip
id={`StructureHover${index}`}
delayShow={250}
place="right"
border={true}
type="light"
>
<span>{label}</span>
</ReactTooltip>
{!isDisabled && (
<Icon
className={`eye-icon ${isVisible && '--visible'}`}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={event => {
event.stopPropagation();
if (isDisabled) {
return;
}
const newVisibility = !isVisible;
setIsVisible(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>
</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>
);
};
StructureSetItem.propTypes = {
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func,
itemClass: PropTypes.string,
color: PropTypes.array.isRequired,
};
StructureSetItem.defaultProps = {
itemClass: '',
onClick: () => {},
};
export default StructureSetItem;
@@ -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;
};
+3
View File
@@ -0,0 +1,3 @@
const id = 'rt';
export default id;
+85
View File
@@ -0,0 +1,85 @@
import React from 'react';
import init from './init.js';
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
import id from './id.js';
import RTPanel from './components/RTPanel/RTPanel';
import { version } from '../package.json';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id,
version,
/**
*
*
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });
},
getPanelModule({ commandsManager, servicesManager, api }) {
const ExtendedRTPanel = props => {
const { activeContexts } = api.hooks.useAppContext();
const contourItemClickHandler = contourData => {
commandsManager.runCommand('jumpToImage', contourData);
};
return (
<RTPanel
{...props}
onContourItemClick={contourItemClickHandler}
activeContexts={activeContexts}
contexts={api.contexts}
/>
);
};
return {
menuOptions: [
{
icon: 'list',
label: 'RTSTRUCT',
target: 'rt-panel',
isDisabled: studies => {
if (!studies) {
return true;
}
for (let i = 0; i < studies.length; i++) {
const study = studies[i];
if (study && study.series) {
for (let j = 0; j < study.series.length; j++) {
const series = study.series[j];
if (
/* Could be expanded to contain RTPLAN and RTDOSE information in the future */
['RTSTRUCT'].includes(series.Modality)
) {
return false;
}
}
}
}
return true;
},
},
],
components: [
{
id: 'rt-panel',
component: ExtendedRTPanel,
},
],
defaultContext: ['VIEWER'],
};
},
getSopClassHandlerModule({ servicesManager }) {
return sopClassHandlerModule;
},
};
+25
View File
@@ -0,0 +1,25 @@
import cornerstoneTools from 'cornerstone-tools';
import RTStructDisplayTool from './tools/RTStructDisplayTool';
import rtStructModule from './tools/modules/rtStructModule';
import TOOL_NAMES from './utils/toolNames';
const defaultConfig = {
TOOL_NAMES: {
RT_STRUCT_DISPLAY_TOOL: 'RTStructDisplayTool',
},
};
/**
*
* @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
export default function init({ servicesManager, configuration = {} }) {
const conifg = Object.assign({}, defaultConfig, configuration);
TOOL_NAMES.RT_STRUCT_DISPLAY_TOOL = conifg.TOOL_NAMES.RT_STRUCT_DISPLAY_TOOL;
cornerstoneTools.register('module', 'rtstruct', rtStructModule);
cornerstoneTools.addTool(RTStructDisplayTool);
}
+326
View File
@@ -0,0 +1,326 @@
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
import TOOL_NAMES from './utils/toolNames';
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
const globalImageIdSpecificToolStateManager =
cornerstoneTools.globalImageIdSpecificToolStateManager;
const { DicomLoaderService } = OHIF.utils;
export default async function loadRTStruct(
rtStructDisplaySet,
referencedDisplaySet,
studies
) {
const rtStructModule = cornerstoneTools.getModule('rtstruct');
// Set here is loading is asynchronous.
// If this function throws its set back to false.
rtStructDisplaySet.isLoaded = true;
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
rtStructDisplaySet,
studies
);
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
const rtStructDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
rtStructDataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
dicomData.meta
);
// global cornerstone tools state to attach measurements to.
const toolState = globalImageIdSpecificToolStateManager.saveToolState();
const {
StructureSetROISequence,
ROIContourSequence,
RTROIObservationsSequence,
StructureSetLabel,
} = rtStructDataset;
// Define our structure set entry and add it to the rtstruct module state.
const structureSet = {
StructureSetLabel,
SeriesInstanceUID: rtStructDataset.SeriesInstanceUID,
ROIContours: [],
referencedSeriesSequence:
rtStructDisplaySet.metadata.ReferencedSeriesSequence,
visible: true,
};
rtStructModule.setters.structureSet(structureSet);
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
);
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
for (let i = 0; i < ROIContourSequence.length; i++) {
const ROIContour = ROIContourSequence[i];
const { ReferencedROINumber, ContourSequence } = ROIContour;
if (!ContourSequence) {
continue;
}
const isSupported = false;
const ContourSequenceArray = _toArray(ContourSequence);
for (let c = 0; c < ContourSequenceArray.length; c++) {
const {
ContourImageSequence,
ContourData,
NumberOfContourPoints,
ContourGeometricType,
} = ContourSequenceArray[c];
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
toolState,
imageId,
rtStructDisplayToolName
);
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
const points = [];
let measurementData;
switch (ContourGeometricType) {
case 'CLOSED_PLANAR':
case 'OPEN_PLANAR':
case 'POINT':
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;
}
}
_setROIContourMetadata(
structureSet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour,
isSupported
);
}
_setToolEnabledIfNotEnabled(rtStructDisplayToolName);
/*
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the rtstruct panel
* or other components that could rely on loaded rtstructs that
* the first batch of structs were loaded so that e.g. when the user opens the panel
* before the structs are fully loaded, the panel can subscribe to this custom event
* and update itself with the new structs.
*
* This limitation is due to the fact that the rtmodule is an object (which will be
* updated after the structs are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the rtstruct loading process in any other component
* without subscribing to external events.
*/
const event = new CustomEvent('extensiondicomrtrtloaded');
document.dispatchEvent(event);
}
function _setROIContourMetadata(
structureSet,
StructureSetROISequence,
RTROIObservationsSequence,
ROIContour,
isSupported
) {
const StructureSetROI = StructureSetROISequence.find(
structureSetROI =>
structureSetROI.ROINumber === ROIContour.ReferencedROINumber
);
const ROIContourData = {
ROINumber: StructureSetROI.ROINumber,
ROIName: StructureSetROI.ROIName,
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
ROIDescription: StructureSetROI.ROIDescription,
isSupported,
visible: true,
};
_setROIContourDataColor(ROIContour, ROIContourData);
if (RTROIObservationsSequence) {
// If present, add additional RTROIObservations metadata.
_setROIContourRTROIObservations(
ROIContourData,
RTROIObservationsSequence,
ROIContour.ReferencedROINumber
);
}
structureSet.ROIContours.push(ROIContourData);
}
function _setROIContourDataColor(ROIContour, ROIContourData) {
let { ROIDisplayColor, RecommendedDisplayCIELabValue } = ROIContour;
if (!ROIDisplayColor && RecommendedDisplayCIELabValue) {
// If ROIDisplayColor is absent, try using the RecommendedDisplayCIELabValue color.
ROIDisplayColor = dicomlab2RGB(RecommendedDisplayCIELabValue);
}
if (ROIDisplayColor) {
ROIContourData.colorArray = [...ROIDisplayColor];
} else {
//Choose a color from the cornerstoneTools colorLUT
// We sample from the default color LUT here (i.e. 0), as we have nothing else to go on.
const { getters } = cornerstoneTools.getModule('segmentation');
const color = getters.colorForSegmentIndexColorLUT(
0,
ROIContourData.ROINumber
);
ROIContourData.colorArray = [...color];
}
}
function _setROIContourRTROIObservations(
ROIContourData,
RTROIObservationsSequence,
ROINumber
) {
const RTROIObservations = RTROIObservationsSequence.find(
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
);
if (RTROIObservations) {
// Deep copy so we don't keep the reference to the dcmjs dataset entry.
const {
ObservationNumber,
ROIObservationDescription,
RTROIInterpretedType,
ROIInterpreter,
} = RTROIObservations;
ROIContourData.RTROIObservations = {
ObservationNumber,
ROIObservationDescription,
RTROIInterpretedType,
ROIInterpreter,
};
}
}
function _setToolEnabledIfNotEnabled(toolName) {
cornerstone.getEnabledElements().forEach(enabledElement => {
const { element, image } = enabledElement;
const tool = cornerstoneTools.getToolForElement(element, toolName);
if (tool.mode !== 'enabled') {
// If not already active or passive, set passive so contours render.
cornerstoneTools.setToolEnabled(toolName);
}
if (image) {
cornerstone.updateImage(element);
}
});
}
function _getOrCreateImageIdSpecificToolData(toolState, imageId, toolName) {
if (toolState.hasOwnProperty(imageId) === false) {
toolState[imageId] = {};
}
const imageIdToolState = toolState[imageId];
// If we don't have tool state for this type of tool, add an empty object
if (imageIdToolState.hasOwnProperty(toolName) === false) {
imageIdToolState[toolName] = {
data: [],
};
}
return imageIdToolState[toolName].data;
}
const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
const imageIdSopInstanceUidPairsEntry = imageIdSopInstanceUidPairs.find(
imageIdSopInstanceUidPairsEntry =>
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
);
return imageIdSopInstanceUidPairsEntry.imageId;
};
function _getImageIdSopInstanceUidPairsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(set => {
return set.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => {
return {
imageId: image.getImageId(),
sopInstanceUID: image.getSOPInstanceUID(),
};
});
}
function _toArray(objOrArray) {
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
}
@@ -0,0 +1,154 @@
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
import TOOL_NAMES from '../utils/toolNames';
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
// Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw');
const drawCircle = importInternal('drawing/drawCircle');
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
const getNewContext = importInternal('drawing/getNewContext');
const BaseTool = importInternal('base/BaseTool');
/**
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
* @extends cornerstoneTools.BaseTool
*/
export default class RTStructDisplayTool extends BaseTool {
constructor(props = {}) {
const defaultProps = {
mixins: ['enabledOrDisabledBinaryTool'],
name: TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL,
};
const initialProps = Object.assign(defaultProps, props);
super(initialProps);
this._rtStructModule = cornerstoneTools.getModule('rtstruct');
}
renderToolData(evt) {
const eventData = evt.detail;
const rtstructModule = this._rtStructModule;
const toolState = getToolState(evt.currentTarget, this.name);
if (!toolState) {
return;
}
const {
lineWidth,
opacity,
highlightOpacity,
} = rtstructModule.configuration;
// We have tool data for this element - iterate over each one and draw it
const context = getNewContext(eventData.canvasContext.canvas);
let crossHairCenter;
for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];
const structureSet = rtstructModule.getters.structureSet(
data.structureSetSeriesInstanceUid
);
// Don't render if entire StructureSet is hidden.
if (structureSet.visible === false) {
continue;
}
const ROIContourData = rtstructModule.getters.ROIContour(
data.structureSetSeriesInstanceUid,
data.ROINumber
);
// Don't render if ROIContour is hidden.
if (ROIContourData.visible === false) {
continue;
}
const points = data.handles.points;
if (!points.length) {
continue;
}
const colorArray = ROIContourData.colorArray;
const color = `rgba(${colorArray[0]},${colorArray[1]},${
colorArray[2]
},${opacity})`;
let highlight = data.highlight;
const options = { color, lineWidth };
if (highlight) {
crossHairCenter = { x: 0, y: 0 };
points.forEach(point => {
crossHairCenter.x += point.x;
crossHairCenter.y += point.y;
});
crossHairCenter.x /= points.length;
crossHairCenter.y /= points.length;
// TODO: Disabling hightlight for now, it'd be good to bring it back
// when we have a good way of doing this for SEG.
// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
// colorArray[2]
// },${highlightOpacity})`;
// Draw highlight lines.
delete data.highlight; // Don't highlight on next render.
}
switch (data.type) {
case 'CLOSED_PLANAR':
this._renderClosedPlanar(context, eventData.element, points, options);
break;
case 'POINT':
this._renderPoint(context, eventData.element, points, options);
break;
case 'OPEN_PLANAR':
this._renderOpenPlanar(context, eventData.element, points, options);
break;
}
}
if (crossHairCenter) {
drawCanvasCrosshairs(eventData, crossHairCenter, {
color: toolColors.getActiveColor(),
lineWidth: 1,
});
}
}
_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);
});
}
}
@@ -0,0 +1,67 @@
import cornerstoneTools from 'cornerstone-tools';
import TOOL_NAMES from '../../../utils/toolNames';
const globalImageIdSpecificToolStateManager =
cornerstoneTools.globalImageIdSpecificToolStateManager;
/**
* getImageIdOfCenterFrameOfROIContour - Returns the imageId nearest to the center of the
* volumes extent in the stack's Z direction which contains one of the ROIContour's Contours.
* @param {string} structureSetSeriesInstanceUid The SeriesInstanceUID of the structure set.
* @param {number} ROINumber The ROINumber of the region.
* @param {string} imageIdsInStack The stack of imageIds.
*
* @returns The imageId
*/
export default function getImageIdOfCenterFrameOfROIContour(
structureSetSeriesInstanceUid,
ROINumber,
imageIdsInStack
) {
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
const toolState = globalImageIdSpecificToolStateManager.saveToolState();
const imageIdIndicies = [];
for (let i = 0; i < imageIdsInStack.length; i++) {
const imageId = imageIdsInStack[i];
const imageIdSpecificToolState = toolState[imageId];
if (
!imageIdSpecificToolState ||
!imageIdSpecificToolState[rtStructDisplayToolName] ||
!imageIdSpecificToolState[rtStructDisplayToolName].data
) {
continue;
}
const toolData = imageIdSpecificToolState[rtStructDisplayToolName].data;
if (
_toolDataContainsROIContour(
toolData,
structureSetSeriesInstanceUid,
ROINumber
)
) {
imageIdIndicies.push(i);
}
}
const centerImageIdIndex =
imageIdIndicies[Math.floor(imageIdIndicies.length / 2)];
return imageIdsInStack[centerImageIdIndex];
}
function _toolDataContainsROIContour(
toolData,
structureSetSeriesInstanceUid,
ROINumber
) {
return !!toolData.some(
toolDataI =>
toolDataI.structureSetSeriesInstanceUid ===
structureSetSeriesInstanceUid && toolDataI.ROINumber === ROINumber
);
}
@@ -0,0 +1,17 @@
/**
* structureSetReferencesSeriesInstanceUid - Returns true if the structure set
* references the given SeriesInstanceUID.
* @param {*} StructureSet
* @param {*} SeriesInstanceUID
*/
export default function structureSetReferencesSeriesInstanceUid(
StructureSet,
SeriesInstanceUID
) {
const { referencedSeriesSequence } = StructureSet;
return referencedSeriesSequence.some(
referencedSeries =>
referencedSeries.SeriesInstanceUID ===
SeriesInstanceUID
);
}
@@ -0,0 +1,187 @@
import cornerstone from 'cornerstone-core';
import getImageIdOfCenterFrameOfROIContour from './lib/getImageIdOfCenterFrameOfROIContour';
import structureSetReferencesSeriesInstanceUid from './lib/structureSetReferencesSeriesInstanceUid';
// We should put this as a helper somewhere as we are using it in mutliple places.
function refreshViewport() {
cornerstone.getEnabledElements().forEach(enabledElement => {
if (enabledElement.image) {
cornerstone.updateImage(enabledElement.element);
}
});
}
const configuration = {
lineWidth: 3,
opacity: 0.75,
highlightOpacity: 0.5,
};
const state = {
StructureSets: [],
};
/**
* Adds a structure set to the module.
* @param {Object} structureSetData The structure set data.
*/
function setStructureSet(structureSetData) {
state.StructureSets.push(structureSetData);
}
/**
* Returns the StructureSet with the given SeriesInstanceUID.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
*
* @returns {Object} The StructureSet.
*/
function getStructureSet(SeriesInstanceUID) {
return state.StructureSets.find(
structureSet => structureSet.SeriesInstanceUID === SeriesInstanceUID
);
}
/**
* Returns the ROI Contour with the given ROINumber on the StructureSet defined by the given
* SeriesInstanceUID.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {number} ROINumber The ROINUmber of the ROIContour to fetch.
*/
function getROIContour(SeriesInstanceUID, ROINumber) {
const structureSet = getStructureSet(SeriesInstanceUID);
if (!structureSet) {
return;
}
return structureSet.ROIContours.find(
ROIContour => ROIContour.ROINumber === ROINumber
);
}
/**
* Hides the StructureSet.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
*/
function setHideStructureSet(SeriesInstanceUID) {
_setStructureSetVisible(SeriesInstanceUID, false);
}
/**
* Shows the StructureSet.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
*/
function setShowStructureSet(SeriesInstanceUID) {
_setStructureSetVisible(SeriesInstanceUID, true);
}
/**
* Sets the visibility of the StructureSet.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {boolean} visible Whether the StructureSet should visible or not.
*/
function _setStructureSetVisible(SeriesInstanceUID, visible = true) {
const StructureSet = getStructureSet(SeriesInstanceUID);
if (StructureSet) {
StructureSet.ROIContours.forEach(ROIContour => {
ROIContour.visible = visible;
});
refreshViewport();
}
}
/**
* Toggles the visibility of the StructureSet.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
*/
function setToggleStructureSet(SeriesInstanceUID) {
const StructureSet = getStructureSet(SeriesInstanceUID);
if (StructureSet) {
StructureSet.visible = !StructureSet.visible;
refreshViewport();
}
}
/**
* Hides the ROIContour.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {number} ROINumber The ROINUmber of the ROIContour.
*/
function setHideROIContour(SeriesInstanceUID, ROINumber) {
_setROIContourVisible(SeriesInstanceUID, ROINumber, false);
}
/**
* Shows the ROIContour.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {number} ROINumber The ROINUmber of the ROIContour.
*/
function setShowROIContour(SeriesInstanceUID, ROINumber) {
_setROIContourVisible(SeriesInstanceUID, ROINumber, true);
}
/**
* Sets the visibility of the ROIContour.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {number} ROINumber The ROINUmber of the ROIContour.
* @param {boolean} visible Whether the StructureSet should visible or not.
*/
function _setROIContourVisible(SeriesInstanceUID, ROINumber, visible = true) {
const ROIContour = getROIContour(SeriesInstanceUID, ROINumber);
if (ROIContour) {
ROIContour.visible = visible;
refreshViewport();
}
}
/**
* Toggles the visibility of the ROIContour.
* @param {string} SeriesInstanceUID The SeriesInstanceUID of the StructureSet.
* @param {number} ROINumber The ROINUmber of the ROIContour.
*/
function setToggleROIContour(SeriesInstanceUID, ROINumber) {
const ROIContour = getROIContour(SeriesInstanceUID, ROINumber);
if (ROIContour) {
ROIContour.visible = !ROIContour.visible;
refreshViewport();
}
}
/**
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
*/
function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
const { StructureSets } = state;
return StructureSets.filter(StructureSet =>
structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
);
}
export default {
getters: {
structureSet: getStructureSet,
ROIContour: getROIContour,
structuresSetsWhichReferenceSeriesInstanceUid: getStructuresSetsWhichReferenceSeriesInstanceUid,
imageIdOfCenterFrameOfROIContour: getImageIdOfCenterFrameOfROIContour,
},
setters: {
structureSet: setStructureSet,
hideROIContour: setHideROIContour,
showROIContour: setShowROIContour,
toggleROIContour: setToggleROIContour,
hideStructureSet: setHideStructureSet,
showStructureSet: setShowStructureSet,
toggleStructureSet: setToggleStructureSet,
},
state,
configuration,
};
@@ -0,0 +1,56 @@
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { importInternal } = cornerstoneTools;
const draw = importInternal('drawing/draw');
const drawLine = importInternal('drawing/drawLine');
const getNewContext = importInternal('drawing/getNewContext');
export default function _drawCanvasCrosshairs(eventData, center, options) {
const context = getNewContext(eventData.canvasContext.canvas);
const { element } = eventData;
const centerCanvas = cornerstone.pixelToCanvas(element, center);
const { clientWidth: width, clientHeight: height } = element;
const offset = 10;
draw(context, context => {
drawLine(
context,
element,
{ x: centerCanvas.x + offset, y: centerCanvas.y },
{ x: width, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x - offset, y: centerCanvas.y },
{ x: 0, y: centerCanvas.y },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y + offset },
{ x: centerCanvas.x, y: height },
options,
'canvas'
);
drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y - offset },
{ x: centerCanvas.x, y: 0 },
options,
'canvas'
);
});
}
@@ -0,0 +1,5 @@
const TOOL_NAMES = {
RTSTRUCT_DISPLAY_TOOL: 'RTStructDisplayTool',
};
export default TOOL_NAMES;
@@ -0,0 +1,91 @@
export default function transformPointsToImagePlane(points, imagePlane) {
// See Equation C.7.6.2.1-1 of the DICOM standard
const {
rowCosines,
columnCosines,
rowPixelSpacing: deltaI,
columnPixelSpacing: deltaJ,
imagePositionPatient,
} = imagePlane;
const X = [rowCosines[0], rowCosines[1], rowCosines[2]];
const Y = [columnCosines[0], columnCosines[1], columnCosines[2]];
const S = [
imagePositionPatient[0],
imagePositionPatient[1],
imagePositionPatient[2],
];
// 9 sets of simulataneous equations to choose from, choose which set to solve
// Based on the largest component of each direction cosine.
// This avoids NaNs or floating point errors caused by dividing by very small numbers and ensures a safe mapping
// when mapping between planes that are close to orthogonal.
let ix = 0;
let iy = 0;
let largestDirectionCosineMagnitude = {
x: 0,
y: 0,
};
// Find the element with the largest magnitude in each direction cosine vector.
for (let i = 0; i < X.length; i++) {
if (Math.abs(X[i]) > largestDirectionCosineMagnitude.x) {
ix = i;
largestDirectionCosineMagnitude.x = Math.abs(X[i]);
}
if (Math.abs(Y[i]) > largestDirectionCosineMagnitude.y) {
iy = i;
largestDirectionCosineMagnitude.y = Math.abs(Y[i]);
}
}
const ci = {
// Index of max elements in X and Y
ix,
iy,
};
// Sanity Check
const directionCosineMagnitude = {
x: Math.pow(X[0], 2) + Math.pow(X[1], 2) + Math.pow(X[2], 2),
y: Math.pow(Y[0], 2) + Math.pow(Y[1], 2) + Math.pow(Y[2], 2),
};
if (directionCosineMagnitude.x < 0.99 || directionCosineMagnitude.y < 0.99) {
throw Error(
`Direction cosines do not sum to 1 in quadrature. There is likely a mistake in the DICOM metadata.` +
`directionCosineMagnitudes: ${directionCosineMagnitude.x}, ${directionCosineMagnitude.y}`
);
}
// Fill in elements that won't change between points
const c = [undefined, Y[ci.ix], X[ci.ix], undefined, X[ci.iy], Y[ci.iy]];
for (let pointI = 0; pointI < points.length; pointI++) {
// Subtract imagePositionPatient from the coordinate
const r = [
points[pointI].x - S[0],
points[pointI].y - S[1],
points[pointI].z - S[2],
];
// Set the variable terms in c.
c[0] = r[ci.ix];
c[3] = r[ci.iy];
// General case: Solves the two choosen simulataneous equations to go from the patient coordinate system to the imagePlane.
const i =
(c[0] - (c[1] * c[3]) / c[5]) /
(c[2] * deltaI * (1 - (c[1] * c[4]) / (c[2] * c[5])));
const j = (c[3] - c[4] * i * deltaI) / (c[5] * deltaJ);
// NOTE: Add (0.5, 0.5) to the coordinate, as PCS reference frame is with respect to the centre of the first pixel.
points[pointI].x = i + 0.5;
points[pointI].y = j + 0.5;
delete points[pointI].z;
}
return;
}
+195
View File
@@ -3,6 +3,201 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.5...@ohif/extension-dicom-segmentation@0.4.6) (2020-09-17)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.4...@ohif/extension-dicom-segmentation@0.4.5) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.3...@ohif/extension-dicom-segmentation@0.4.4) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.2...@ohif/extension-dicom-segmentation@0.4.3) (2020-09-03)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.1...@ohif/extension-dicom-segmentation@0.4.2) (2020-09-02)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [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)
### Bug Fixes
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.4...@ohif/extension-dicom-segmentation@0.2.5) (2020-05-06)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.3...@ohif/extension-dicom-segmentation@0.2.4) (2020-05-04)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.2...@ohif/extension-dicom-segmentation@0.2.3) (2020-05-04)
### Bug Fixes
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.1...@ohif/extension-dicom-segmentation@0.2.2) (2020-04-28)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.0...@ohif/extension-dicom-segmentation@0.2.1) (2020-04-27)
### Bug Fixes
* 🐛 Dropdown not updating when selecting segment ([#1675](https://github.com/OHIF/Viewers/issues/1675)) ([20e366f](https://github.com/OHIF/Viewers/commit/20e366ffbd76781f48dc99c9f59e24b255b5e5df)), closes [#1669](https://github.com/OHIF/Viewers/issues/1669)
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.2...@ohif/extension-dicom-segmentation@0.2.0) (2020-04-24)
### Features
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.1...@ohif/extension-dicom-segmentation@0.1.2) (2020-04-09)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.0...@ohif/extension-dicom-segmentation@0.1.1) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
# 0.1.0 (2020-03-13)
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.1.0",
"version": "0.4.6",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -30,14 +30,16 @@
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5",
"dcmjs": "^0.6.1",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.3",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"react-select": "^3.0.8"
"gl-matrix": "^3.3.0",
"react-select": "^3.0.8",
"react-tooltip": "4.2.10"
}
}
@@ -1,71 +0,0 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class.
const OHIFDicomSegSopClassHandler = {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = metadata;
const segDisplaySet = {
Modality: 'SEG',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesDescription,
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(
segDisplaySet,
referencedDisplaySet,
studies
).catch(error => {
segDisplaySet.isLoaded = false;
throw new Error(error);
});
};
return segDisplaySet;
},
};
export default OHIFDicomSegSopClassHandler;
@@ -12,14 +12,6 @@
height: 20px;
}
.dcmseg-segment-item .segment-label {
overflow: hidden;
max-width: calc(100% - 50px);
text-overflow: ellipsis;
overflow-wrap: normal;
white-space: nowrap;
}
.dcmseg-segment-item .segment-info {
display: inline-block;
margin-top: 9px;
@@ -62,3 +54,30 @@
.dcmseg-segment-item .segment-actions .btnAction i {
margin-right: 4px;
}
.dcmseg-segment-item .segment-label {
display: flex;
justify-content: space-between;
padding-right: 20px;
}
.dcmseg-segment-item .segment-label span {
overflow-wrap: normal;
white-space: nowrap;
overflow: hidden;
max-width: calc(100% - 40px); /* calc(100% - 50px); 20px = eye icon */
text-overflow: ellipsis;
}
.dcmseg-segment-item .segment-label .eye-icon {
cursor: pointer;
color: var(--active-color);
}
.dcmseg-segment-item .segment-label .eye-icon:hover {
color: var(--hover-color);
}
.dcmseg-segment-item .segment-label .eye-icon.--visible {
color: var(--default-color);
}
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui';
import ReactTooltip from 'react-tooltip';
import './SegmentItem.css';
@@ -17,48 +18,87 @@ ColoredCircle.propTypes = {
color: PropTypes.array.isRequired,
};
const SegmentItem = ({ index, label, onClick, itemClass, color }) => (
<div className="dcmseg-segment-item">
<TableListItem
key={index}
itemKey={index}
itemIndex={index}
itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section"
onItemClick={onClick}
>
<div>
<div className="segment-label" style={{ marginBottom: 4 }}>
{label}
</div>
{false && <div className="segment-info">{'...'}</div>}
{false && (
<div className="segment-actions">
<button
className="btnAction"
onClick={() => console.log('Relabelling...')}
const SegmentItem = ({
index,
label,
onClick,
itemClass,
color,
visible = true,
onVisibilityChange,
}) => {
const [isVisible, setIsVisible] = useState(visible);
useEffect(() => {
setIsVisible(visible);
}, [visible]);
return (
<div className="dcmseg-segment-item">
<TableListItem
key={index}
itemKey={index}
itemIndex={index}
itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section"
onItemClick={onClick}
>
<div>
<div className="segment-label" style={{ marginBottom: 4 }}>
<a data-tip data-for={`SegmentHover${index}`}>
<span>{label}</span>
</a>
<ReactTooltip
id={`SegmentHover${index}`}
delayShow={250}
place="right"
border={true}
type="light"
>
<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>
<span>{label}</span>
</ReactTooltip>
<Icon
className={`eye-icon ${isVisible && '--visible'}`}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={event => {
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
}}
/>
</div>
)}
</div>
</TableListItem>
</div>
);
{false && <div className="segment-info">{'...'}</div>}
{false && (
<div className="segment-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>
</div>
);
};
SegmentItem.propTypes = {
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
@@ -70,7 +110,7 @@ SegmentItem.propTypes = {
SegmentItem.defaultProps = {
itemClass: '',
onClick: () => { }
onClick: () => {},
};
export default SegmentItem;
@@ -8,6 +8,11 @@
flex-direction: column;
}
.dcmseg-segmentation-panel.disabled {
opacity: 0.6;
pointer-events: none;
}
.dcmseg-segmentation-panel h3 {
padding-left: 20px;
margin-top: 0;
@@ -1,10 +1,14 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import moment from 'moment';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
import setActiveLabelmap from '../../utils/setActiveLabelMap';
import refreshViewports from '../../utils/refreshViewports';
import {
BrushColorSelector,
@@ -19,72 +23,97 @@ import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
const { studyMetadataManager } = utils;
const refreshViewport = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
};
/**
* SegmentationPanel component
*
* @param {Object} props
* @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData
* @param {number} props.activeIndex - activeViewportIndex
* @param {Array} props.studies - Studies data
* @param {Array} props.viewports - Viewports data (viewportSpecificData)
* @param {number} props.activeIndex - Active viewport index
* @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded
* @param {Function} props.onSegmentItemClick - Segment click handler
* @param {Function} props.onSegmentVisibilityChange - Segment visibiliy change handler
* @param {Function} props.onConfigurationChange - Configuration change handler
* @param {Function} props.activeContexts - List of active application contexts
* @param {Function} props.contexts - List of available application contexts
* @returns component
*/
const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
const SegmentationPanel = ({
studies,
viewports,
activeIndex,
isOpen,
onSegmentItemClick,
onSegmentVisibilityChange,
onConfigurationChange,
onDisplaySetLoadFailure,
onSelectedSegmentationChange,
activeContexts = [],
contexts = {},
}) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
/*
* TODO: wrap get/set interactions with the cornerstoneTools
* store with context to make these kind of things less blurry.
*/
const segmentationModule = cornerstoneTools.getModule('segmentation');
const { configuration } = segmentationModule;
const { configuration } = cornerstoneTools.getModule('segmentation');
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
const [brushRadius, setBrushRadius] = useState(DEFAULT_BRUSH_RADIUS);
/* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
const [brushColor, setBrushColor] = useState('rgba(221, 85, 85, 1)');
const [selectedSegment, setSelectedSegment] = useState();
const [showSegSettings, setShowSegSettings] = useState(false);
const [selectedSegmentation, setSelectedSegmentation] = useState();
const viewport = viewports[activeIndex];
const {
StudyInstanceUID,
SeriesInstanceUID,
displaySetInstanceUID,
} = viewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const firstImageId = studyMetadata.getFirstImageId(displaySetInstanceUID);
/* CornerstoneTools */
const [brushStackState, setBrushStackState] = useState(
segmentationModule.state.series[firstImageId]
);
/*
* TODO: We shouldn't hardcode brushColor color, in the future
* the SEG may set the colorLUT to whatever it wants.
*/
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)',
selectedSegment: null,
selectedSegmentation: null,
showSegmentationSettings: false,
brushStackState: null,
labelmapList: [],
segmentList: [],
cachedSegmentsProperties: [],
isLoading: false,
isDisabled: true,
});
useEffect(() => {
setShowSegSettings(showSegSettings && !isOpen);
}, [isOpen]);
useEffect(() => {
setBrushStackState(segmentationModule.state.series[firstImageId]);
}, [studies, viewports, activeIndex, firstImageId]);
useEffect(() => {
if (brushStackState) {
setSelectedSegmentation(brushStackState.activeLabelmapIndex);
}
const labelmapModifiedHandler = event => {
log.warn('labelmap modified', event);
setBrushStackState(segmentationModule.state.series[firstImageId]);
log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
updateState('brushStackState', module.state.series[firstImageId]);
};
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
document.addEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
/*
* These are specific to each element;
* Need to iterate cornerstone-tools tracked enabled elements?
@@ -98,6 +127,10 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
);
return () => {
document.removeEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified',
@@ -105,144 +138,335 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
)
);
};
});
}, [activeIndex, viewports]);
if (!brushStackState) {
return null;
}
const refreshSegmentations = useCallback(() => {
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
if (!isDisabled) {
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
const brushStackState = module.state.series[firstImageId];
if (brushStackState) {
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const labelmapList = getLabelmapList(
brushStackState,
firstImageId,
activeViewport
);
const segmentList = getSegmentList(
labelmap3D,
firstImageId,
brushStackState
);
setState(state => ({
...state,
brushStackState,
selectedSegmentation: brushStackState.activeLabelmapIndex,
labelmapList,
segmentList,
isDisabled,
}));
} else {
setState(state => ({
...state,
labelmapList: [],
segmentList: [],
isDisabled,
}));
}
}
}, [viewports, activeIndex, state.isLoading]);
/*
* 2. UseEffect to update state? or to a least trigger a re-render
* 4. Toggle visibility of labelmap?
* 5. Toggle visibility of seg?
*
* If the port is cornerstone, just need to call a re-render.
* If the port is vtkjs, its a bit more tricky as we now need to create a new
*/
useEffect(() => {
refreshSegmentations();
}, [
viewports,
activeIndex,
isOpen,
state.selectedSegmentation,
activeContexts,
state.isLoading,
]);
const getLabelmapList = () => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
StudyInstanceUID,
SeriesInstanceUID
);
/* Handle open/closed panel behaviour */
useEffect(() => {
setState(state => ({
...state,
showSegmentationSettings: state.showSegmentationSettings && !isOpen,
}));
}, [isOpen]);
return referencedSegDisplaysets.map((displaySet, index) => {
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
const getLabelmapList = useCallback(
(brushStackState, firstImageId, activeViewport) => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const isActiveLabelmap =
labelmapIndex === brushStackState.activeLabelmapIndex;
const displayDate = date.format('ddd, MMM Do YYYY');
const displayTime = date.format('h:mm:ss a');
const displayDescription = displaySet.SeriesDescription;
return referencedSegDisplaysets.map((displaySet, index) => {
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
return {
value: labelmapIndex,
title: displayDescription,
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await _setActiveLabelmap(
viewport,
studies,
displaySet,
firstImageId,
brushStackState.activeLabelmapIndex
);
setSelectedSegmentation(activatedLabelmapIndex);
},
};
});
};
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const isActiveLabelmap =
labelmapIndex === brushStackState.activeLabelmapIndex;
const displayDate = date.format('ddd, MMM Do YYYY');
const displayTime = date.format('h:mm:ss a');
const displayDescription = displaySet.SeriesDescription;
const labelmapList = getLabelmapList();
return {
value: labelmapIndex,
title: displayDescription,
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await setActiveLabelmap(
activeViewport,
studies,
displaySet,
() => onSelectedSegmentationChange(),
onDisplaySetLoadFailure
);
updateState('selectedSegmentation', activatedLabelmapIndex);
},
};
});
},
[studies]
);
const segmentList = [];
const getSegmentList = useCallback(
(labelmap3D, firstImageId, brushStackState) => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
if (labelmap3D) {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
);
updateState('selectedSegment', sameSegment ? null : segmentNumber);
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const validIndexList = [];
labelmap3D.labelmaps2D.forEach((labelMap2D, index) => {
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
validIndexList.push(index);
}
});
const avg = array => array.reduce((a, b) => a + b) / array.length;
const average = avg(validIndexList);
const closest = validIndexList.reduce((prev, curr) => {
return Math.abs(curr - average) < Math.abs(prev - average)
? curr
: prev;
});
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
if (!toolState) {
return;
}
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
const imageIds = toolState.data[0].imageIds;
const imageId = imageIds[closest];
const frameIndex = imageIds.indexOf(imageId);
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
DICOMSegTempCrosshairsTool.addCrosshair(
element,
imageId,
segmentNumber
);
onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex,
});
}
if (isVTK()) {
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const allDisplaySets = studyMetadata.getDisplaySets();
const currentDisplaySet = allDisplaySets.find(
displaySet =>
displaySet.displaySetInstanceUID ===
activeViewport.displaySetInstanceUID
);
const frame = labelmap3D.labelmaps2D[closest];
onSegmentItemClick({
studies,
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
segmentNumber,
frameIndex: closest,
frame,
});
}
};
const isSegmentVisible = () => {
return !labelmap3D.segmentsHidden[segmentIndex];
};
const toggleSegmentVisibility = () => {
const segmentsHidden = labelmap3D.segmentsHidden;
segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
return !segmentsHidden[segmentIndex];
};
const cachedSegmentProperties =
state.cachedSegmentsProperties[segmentNumber];
let visible = isSegmentVisible();
if (
cachedSegmentProperties &&
cachedSegmentProperties.visible !== visible
) {
toggleSegmentVisibility();
}
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={() => setCurrentSelectedSegment()}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={visible}
onVisibilityChange={newVisibility => {
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
module.setters.toggleSegmentVisibility(
element,
segmentNumber,
brushStackState.activeLabelmapIndex
);
}
if (isVTK()) {
onSegmentVisibilityChange(segmentNumber, newVisibility);
}
updateCachedSegmentsProperties(segmentNumber, {
visible: newVisibility,
});
refreshViewports();
}}
/>
);
}
const sameSegment = selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
);
setSelectedSegment(sameSegment ? null : segmentNumber);
};
return segmentList;
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment}
label={segmentLabel}
index={segmentNumber}
color={color}
/>
);
}
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
},
[activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
);
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
}
const updateCachedSegmentsProperties = (segmentNumber, properties) => {
const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] = segmentProperties
? { ...segmentProperties, ...properties }
: properties;
updateState('cachedSegmentsProperties', segmentsProperties);
};
useEffect(() => {
updateState('cachedSegmentsProperties', []);
}, [activeContexts]);
const updateState = (field, value) => {
setState(state => ({ ...state, [field]: value }));
};
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
setBrushRadius(updatedRadius);
segmentationModule.setters.radius(updatedRadius);
updateState('brushRadius', updatedRadius);
const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius);
}
};
@@ -251,36 +475,35 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
if (labelmap3D.activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--;
}
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const incrementSegment = event => {
event.preventDefault();
labelmap3D.activeSegmentIndex++;
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
setBrushColor(color);
updateState('brushColor', color);
};
const getActiveSegmentColor = () => {
if (!brushStackState) {
if (!state.brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`;
};
const updateConfiguration = newConfiguration => {
/* Supported configuration */
configuration.renderFill = newConfiguration.renderFill;
configuration.renderOutline = newConfiguration.renderOutline;
configuration.shouldRenderInactiveLabelmaps =
@@ -290,32 +513,41 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
configuration.outlineWidth = newConfiguration.outlineWidth;
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
refreshViewport();
onConfigurationChange(newConfiguration);
refreshViewports();
};
if (showSegSettings) {
const disabledConfigurationFields = [
'outlineAlpha',
'shouldRenderInactiveLabelmaps',
];
if (state.showSegmentationSettings) {
return (
<SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration}
onBack={() => setShowSegSettings(false)}
onBack={() => updateState('showSegmentationSettings', false)}
onChange={updateConfiguration}
/>
);
} else {
return (
<div className="dcmseg-segmentation-panel">
<div
className={`dcmseg-segmentation-panel ${state.isDisabled &&
'disabled'}`}
>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSegSettings(true)}
onClick={() => updateState('showSegmentationSettings', true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
index={labelmap3D.activeSegmentIndex}
index={state.selectedSegment}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
@@ -330,18 +562,18 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
<h3>Segmentations</h3>
<div className="segmentations">
<SegmentationSelect
value={
labelmapList.find(i => i.value === selectedSegmentation) || null
}
value={state.labelmapList.find(
i => i.value === state.selectedSegmentation
)}
formatOptionLabel={SegmentationItem}
options={labelmapList}
options={state.labelmapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={segmentList.length} />}
customHeader={<SegmentsHeader count={state.segmentList.length} />}
>
{segmentList}
{state.segmentList}
</TableList>
</ScrollableArea>
</div>
@@ -373,6 +605,7 @@ SegmentationPanel.propTypes = {
}),
activeIndex: PropTypes.number.isRequired,
studies: PropTypes.array.isRequired,
isOpen: PropTypes.bool.isRequired,
};
SegmentationPanel.defaultProps = {};
@@ -401,44 +634,6 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets;
};
/**
*
*
* @param {*} viewportSpecificData
* @param {*} studies
* @param {*} displaySet
* @param {*} firstImageId
* @param {*} activeLabelmapIndex
* @returns
*/
const _setActiveLabelmap = async (
viewportSpecificData,
studies,
displaySet,
firstImageId,
activeLabelmapIndex
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return;
}
if (!displaySet.isLoaded) {
// What props does this expect `viewportSpecificData` to have?
// TODO: Should this return the `labelmapIndex`?
await displaySet.load(viewportSpecificData, studies);
}
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewport();
return displaySet.labelmapIndex;
};
/**
*
* @param {*} firstImageId
@@ -458,7 +653,7 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmap3D.activeSegmentIndex = segmentIndex;
refreshViewport();
refreshViewports();
return segmentIndex;
};
@@ -17,6 +17,10 @@ const defaultColor = computedstyle.getPropertyValue('--default-color');
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
const segmentationSelectStyles = {
singleValue: (base, state) => ({
...base,
width: '100%'
}),
control: (base, state) => ({
...base,
cursor: 'pointer',
@@ -4,7 +4,7 @@ import { Range } from '@ohif/ui';
import './SegmentationSettings.css';
const SegmentationSettings = ({ configuration, onBack, onChange }) => {
const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
const [state, setState] = useState({
renderFill: configuration.renderFill,
renderOutline: configuration.renderOutline,
@@ -70,28 +70,32 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
/>
{state.renderOutline && (
<>
<CustomRange
value={state.outlineAlpha * 100}
label="Opacity"
showPercentage
step={1}
min={0}
max={100}
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
/>
<CustomRange
value={state.outlineWidth}
label="Width"
showValue
step={1}
min={0}
max={5}
onChange={event => save('outlineWidth', parseInt(event.target.value))}
/>
{!disabledFields.includes('outlineAlpha') && (
<CustomRange
value={state.outlineAlpha * 100}
label="Opacity"
showPercentage
step={1}
min={0}
max={100}
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
/>
)}
{!disabledFields.includes('outlineWidth') && (
<CustomRange
value={state.outlineWidth}
label="Width"
showValue
step={1}
min={0}
max={5}
onChange={event => save('outlineWidth', parseInt(event.target.value))}
/>
)}
</>
)}
</div>
{(state.renderFill || state.renderOutline) && (
{(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
<div
className="settings-group"
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
@@ -103,7 +107,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
/>
{state.shouldRenderInactiveLabelmaps && (
<>
{state.renderFill && (
{state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
<CustomRange
label="Fill Opacity"
showPercentage
@@ -114,7 +118,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
/>
)}
{state.renderOutline && (
{state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
<CustomRange
label="Outline Opacity"
showPercentage
@@ -0,0 +1,72 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
import getSourceDisplaySet from './getSourceDisplaySet';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
export default function getSopClassHandlerModule({ servicesManager }) {
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class.
return {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SeriesNumber,
} = metadata;
const segDisplaySet = {
Modality: 'SEG',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesNumber,
SeriesDescription,
metadata,
};
segDisplaySet.getSourceDisplaySet = function(studies) {
return getSourceDisplaySet(studies, segDisplaySet);
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
};
return segDisplaySet;
},
};
}
@@ -0,0 +1,88 @@
import setActiveLabelmap from './utils/setActiveLabelMap';
import { classes } from '@ohif/core';
const { ImageSet } = classes;
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 { metadata } = segDisplaySet;
let referencedSeriesInstanceUIDs;
if (metadata.ReferencedSeriesSequence) {
const ReferencedSeriesSequence = _toArray(
metadata.ReferencedSeriesSequence
);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
} else {
const { PerFrameFunctionalGroupsSequence } = metadata;
let SourceImageSequence;
if (metadata.SourceImageSequence) {
SourceImageSequence = metadata.SourceImageSequence;
} else {
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
}
const firstSourceImage = _toArray(SourceImageSequence)[0];
const { ReferencedSOPInstanceUID } = firstSourceImage;
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
otherDisplaySets,
ReferencedSOPInstanceUID
);
}
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
return referencedDisplaySet;
};
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
displaySets,
SOPInstanceUID
) => {
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
for (let i = 0; i < imageSets.length; i++) {
const { images } = imageSets[i];
for (let j = 0; j < images.length; j++) {
if (images[j].SOPInstanceUID === SOPInstanceUID) {
return [images[j].getData().metadata.SeriesInstanceUID];
}
}
}
};
function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
}
Loaded 100 of 294 files, more files were not shown because too many files have changed in this diff. Show more