Compare commits

...
Author SHA1 Message Date
ohif-bot 3f7eb8a5d3 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.14
 - @ohif/core@2.3.8
 - @ohif/viewer@3.4.14
2020-02-06 02:56:29 +00:00
Gustavo André LelisandDanny Brown 7b2d44f2c1 fix: Remove trash data from redux storage after updates (#1358)
* Remove tash data from redux storage after updates

* Imprelemnted more unit tests

* Remove missing comment

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-02-05 21:54:57 -05:00
ohif-bot 225da81135 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.13
 - @ohif/core@2.3.7
 - @ohif/viewer@3.4.13
2020-01-30 18:33:22 +00:00
Rodrigo AntinarelliandDanny Brown 686d12da5c fix: Set VTK viewport as active by interaction (#1139)
* fix: Set VTK viewport as active by interaction

* feat: listen for vtkscrollevent in wrapper component

* fix: definitions can skip empty storeContexts key

* hoc to set/pass in commandsManager

* Bump minimum react-vtkjs-viewport version to leverage new event

* Simplify to use onScroll event instead of passing down commandsManager to base component

* fix: make sure we include @JamesAPetts bug fix

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-30 13:26:06 -05:00
ohif-bot c22756051b chore(release): publish [skip ci]
- @ohif/viewer@3.4.12
2020-01-30 03:46:42 +00:00
8fdfcc0c7d chore: Remove unused dependency on favicons-webpack-plugin. (#1351)
This dependency depends on the `favicons` library which depends on
`sharp` which depends on a native library.

This complicates the build process as the library is built from source
because there's no binary available for my macOS Catalina
installation. As this is a common developer setup I expect others to
run into this issue as well, which is why I opted to fix this issue.

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2020-01-29 22:43:15 -05:00
ohif-bot 44ab65c981 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.12
 - @ohif/i18n@0.52.5
 - @ohif/ui@1.1.9
 - @ohif/viewer@3.4.11
2020-01-30 03:42:31 +00:00
b9574b6efc fix: download tool fixes & improvements (#1235)
* fix: download tool fixes & improvements

* fix filename error

* Create variable to track erros

* Fix small console error

* Fix all conflicts and merge changes from latest master with this PR's improvements

* Small improvement on select and textinput labels

* Add new icon for unlink

* Refactor on download image modal

* Adding loading screen

* Fix translation issue

* Fixing aspect Ratio and E2E tests

* Allow empty value without setting to 0

* Remove eslint comments

* Fixing typos

Co-authored-by: Gustavo André Lelis <galelis@gmail.com>
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-29 22:39:44 -05:00
ohif-bot 96a8e26786 chore(release): publish [skip ci]
- @ohif/viewer@3.4.10
2020-01-28 21:32:03 +00:00
Mirna SilvaandDanny Brown 80f5b23622 test: 1316 - E2E tests for /local (#1379)
* 1316 - E2E tests for /local

* fix test title

* update yarn.lock

* Increased timeout for thumbnails

* Removing PDF file test from script-tag server

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-28 16:29:17 -05:00
ohif-bot 2b4950e954 chore(release): publish [skip ci]
- @ohif/viewer@3.4.9
2020-01-28 19:18:20 +00:00
dependabot[bot] e16f14089d chore(deps-dev): bump lodash from 4.17.11 to 4.17.15 in /platform/viewer (#1403)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-28 14:15:59 -05:00
Danny Brown c3807a89ed chore: exclude extension discussion 2020-01-28 12:46:05 -05:00
Danny Brown 049ef4a666 chore: scare away stale bot 2020-01-28 12:43:07 -05:00
ohif-bot d5fa880a29 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.11
 - @ohif/core@2.3.6
 - @ohif/viewer@3.4.8
2020-01-28 14:03:08 +00:00
Erik Ziegler 1b9a6db73f Handle Palette Color images where LUT has been returned as InlineBinary (#1350) (#1401) 2020-01-28 15:01:13 +01:00
ohif-bot ed2692908e chore(release): publish [skip ci]
- @ohif/viewer@3.4.7
2020-01-28 12:54:21 +00:00
Joël Spaltenstein 0c6c5a5d51 OIDC Initiating Login from a Third Party
https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
2020-01-28 13:50:54 +01:00
ohif-bot b449f8eb0e chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.2.2
 - @ohif/extension-vtk@1.2.10
 - @ohif/core@2.3.5
 - @ohif/viewer@3.4.6
2020-01-28 12:41:49 +00:00
Erik Ziegler cf5160fb13 Add DICOM Group 6xxx Overlay Plane rendering (#1402)
* Add DICOM Group 6xxx Overlay Plane rendering

* Bump to cornerstone-tools 4.12.0 to support DICOM 6xxx Overlays
2020-01-28 13:40:03 +01:00
ohif-bot 4c1c91c006 chore(release): publish [skip ci]
- @ohif/viewer@3.4.5
2020-01-27 16:10:06 +00:00
Mirna Silva 9ed39c149b chore: Removing dependency ffmpeg-installer (#1397)
Fixes FOSSA warning/failure
2020-01-27 11:06:51 -05:00
ohif-bot b3f0fe029d chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.9
 - @ohif/core@2.3.4
 - @ohif/viewer@3.4.4
2020-01-27 09:14:56 +00:00
Erik Ziegler a1b78afdb9 Add palette color cache object back to core (fix #1350) (#1395) 2020-01-27 10:12:07 +01:00
Mirna Silva 3d3cfb1b41 Merge pull request #1393 from mirnasilva/fix_NewPatientSaveMeasurement
Fix on CI - Choosing another Patient Study
2020-01-24 16:17:10 -03:00
Mirna Silva b812fe03d2 Fix on CI - Changing to another Patient Study 2020-01-24 15:56:25 -03:00
ohif-bot 84f087bca4 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.8
 - @ohif/core@2.3.3
 - @ohif/viewer@3.4.3
2020-01-24 10:33:21 +00:00
Igor Octaviano a7e612eb88 refactor: 💡 Clean up JSON launch with DICOMWeb server (#1388)
* refactor: 💡 Clean up JSON launch with DICOMWeb server

Clean up JSON launch with DICOMWeb server

✅ Closes: #1387

* Remove test file

* parse seriesinstanceuids in query

* Add default studies

* Remove test file

* Cleanup retrievedata changes

* Set active server
2020-01-24 10:31:22 +00:00
Mirna Silva 9e3afda2f1 Merge pull request #1384 from mirnasilva/percy-ci-fix
Visual tests: Fixed ci config for visual tests (percy)
2020-01-20 11:13:24 -03:00
Mirna Silva 4d7a91a584 Merge branch 'master' into percy-ci-fix 2020-01-17 20:48:51 -03:00
Mirna Silva 1dc320edcd Fixed ci config for visual tests 2020-01-17 20:44:45 -03:00
ohif-bot 1d02f5485f chore(release): publish [skip ci]
- @ohif/viewer@3.4.2
2020-01-17 22:45:01 +00:00
Mirna Silva 4c9a977920 Merge pull request #1382 from OHIF/mirna/test/visual-tests
Percy fix for CI failure
2020-01-17 19:43:19 -03:00
Mirna Silva b5db09c25e Merge pull request #1383 from mirnasilva/percy-ci-fix
Percy ci fix
2020-01-17 19:14:16 -03:00
Mirna Silva 8dc4a5b82a Merge branch 'percy-ci-fix' of https://github.com/mirnasilva/Viewers into percy-ci-fix 2020-01-17 16:36:08 -03:00
Mirna Silva 44add9ce2f Fix for save measurements failure 2020-01-17 16:35:26 -03:00
Mirna Silva c19eceeede Merge pull request #1381 from mirnasilva/percy-ci-fix
Percy tests - fix for CI failure
2020-01-17 14:46:43 -03:00
Mirna Silva 72bc5a5ae5 Merge branch 'master' into percy-ci-fix 2020-01-17 14:45:13 -03:00
Mirna Silva dfdf46dbdd fix for CI failure 2020-01-17 14:31:53 -03:00
ohif-bot 571dff2c09 chore(release): publish [skip ci]
- @ohif/viewer@3.4.1
2020-01-15 14:16:43 +00:00
Gustavo André Lelis b9a66d4424 fix: 🐛 Metadata is being mistakenly purged (#1360)
Closes: #1326
2020-01-15 09:15:10 -05:00
ohif-bot 7ab2e3e60d chore(release): publish [skip ci]
- @ohif/viewer@3.4.0
2020-01-14 15:26:35 +00:00
mukhlin a5d6bc6a51 feat: Custom Healthcare API endpoint (#1367)
* Add ability to configure Google Cloud Healthcare API endpoint.

* Fixed bug in script.

* Fixed PR remark.

* Set Healthcare API endpoint in proper place.
2020-01-14 10:22:53 -05:00
ohif-bot df612ca4a0 chore(release): publish [skip ci]
- @ohif/viewer@3.3.8
2020-01-10 18:00:27 +00:00
Mirna Silva a91ce57c43 Merge pull request #1363 from OHIF/task/improveVisualTest
Task/improve visual tests. Related to PR #1327
2020-01-10 14:57:33 -03:00
Mirna Silva 342884f83e Merge pull request #1327 from mirnasilva/1320_ImproveVisualRegression
E2E test: Percy visual regression improvements
2020-01-10 14:30:01 -03:00
Mirna Silva 0fb39bad84 Changes on config file 2020-01-10 14:18:20 -03:00
Mirna Silva c74c80bb6f Fix for VTK test 2020-01-09 16:46:29 -03:00
Mirna Silva 910d5c65d5 Changing RELEASE config to run only percy tests 2020-01-09 16:17:22 -03:00
Mirna Silva d0fb4843ab Changing screenshot paths back to original 2020-01-09 15:29:39 -03:00
Mirna Silva f488b857fe Merge branch '1320_ImproveVisualRegression' of https://github.com/mirnasilva/Viewers into 1320_ImproveVisualRegression 2020-01-09 14:50:40 -03:00
Mirna Silva 256e8b1116 Merge branch 'master' into 1320_ImproveVisualRegression 2020-01-09 14:49:42 -03:00
Mirna Silva 168dd87747 Merge branch 'master' of https://github.com/mirnasilva/Viewers into 1320_ImproveVisualRegression 2020-01-08 15:21:43 -03:00
ohif-bot 13c41e6d86 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.7
 - @ohif/ui@1.1.8
 - @ohif/viewer@3.3.7
2020-01-08 13:48:10 +00:00
barhantasandDanny Brown 7ab0bbb325 fix: measurements panel css and delete button visibility (#1352)
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-08 08:43:30 -05:00
ohif-bot 2213187b14 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.6
 - @ohif/viewer@3.3.6
2020-01-07 22:44:05 +00:00
Maher Jendoubi 4c4d8a61b6 chore: Fix a typo (#1353) 2020-01-07 17:41:16 -05:00
Mirna Silva 67f58f5f09 Added dependency ffmpeg-installer 2020-01-07 19:09:04 -03:00
Mirna Silva dfc35885bb Changed record to false 2020-01-07 18:28:16 -03:00
Mirna Silva e9c449d1db Changed path for cypress/videos 2020-01-07 18:07:20 -03:00
Mirna Silva 6dac50c7c6 Improvements on VTK visual tests 2 2020-01-07 17:23:35 -03:00
Mirna Silva 9be54f6164 Improvements on VTK visual tests 2020-01-07 16:52:28 -03:00
Mirna Silva 341cc5a4eb Merge branch 'master' into 1320_ImproveVisualRegression 2020-01-06 19:07:20 -03:00
Mirna Silva 7b80171203 Removing unnecessary screenshots 2020-01-06 18:54:09 -03:00
ohif-bot 2e5a385e53 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.5
 - @ohif/core@2.3.2
 - @ohif/viewer@3.3.5
2020-01-06 21:29:37 +00:00
Danny Brown a3caeb381e chore: remove unused file; duplicated by studyInstanceHelpers (#1349)
* chore: remove unused file; duplicated by studyInstanceHelpers

* Change expected study list results num
2020-01-06 16:26:57 -05:00
Mirna Silva ab0af86379 Merge branch '1320_ImproveVisualRegression' of https://github.com/mirnasilva/Viewers into 1320_ImproveVisualRegression 2020-01-06 17:09:24 -03:00
Mirna Silva 86426cb024 Changed command CI line for diff test paths 2020-01-06 17:08:00 -03:00
Mirna Silva 348840ef26 Merge branch 'master' of https://github.com/mirnasilva/Viewers into 1320_ImproveVisualRegression 2020-01-06 16:20:11 -03:00
Mirna Silva 52ef934961 Merge branch 'master' into 1320_ImproveVisualRegression 2020-01-06 14:48:30 -03:00
ohif-bot be995f93b6 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.4
 - @ohif/core@2.3.1
 - @ohif/viewer@3.3.4
2019-12-30 20:01:54 +00:00
Danny Brown 5036615e00 chore: throw our error if we encounter one (no swallow) (#1337) 2019-12-30 14:53:39 -05:00
Mirna Silva 4ee2b135a7 Added more tests and updated CI config.yml 2019-12-24 01:17:02 -03:00
Mirna Silva 388fe04e09 E2E test: Percy visua regression improvements 2019-12-23 23:10:55 -03:00
Gustavo André Lelis 2e215aed4c test: 💍 Updating tests after bug fix (#1324)
Closes: #1167
2019-12-20 19:14:07 -05:00
ohif-bot ffd27d6db4 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.2.1
 - @ohif/extension-vtk@1.2.3
 - @ohif/viewer@3.3.3
2019-12-20 21:01:26 +00:00
Gustavo André LelisandDanny Brown 6540e36818 fix: 🐛 1241: Make Plugin switch part of ToolbarModule (#1322)
* fix: 🐛 Make Plugin switch part of ToolbarModule

Closes: #1241

* Move setCornerstoneViewport command into cornerstone extension

* Exposing extensionManager into extension's get modules functions

* Remove Plugin switch

* Updating set cornerstonelayout to work on VIEWER context

* Creating a customComponent for 2D MPR button

* Use extensionManager to show or not buttons
Adding isHidden option into toolbarModules

* Make buttons hidden depending on extension registration

* Fix unit test after changes on extensionManager

* Remove workaround for extensionManager

* Use studyMeatadataManager and remove TODO's

* Remove storeContext not needed

* Moving Exit 2D MPR into cornerstone extension with VTK context

* Remove PluginSwitch files

* Fix E2E tests

* Adding cypress screenshots into gitignore

* Remove extensionManager from getModuleFn functions and moving CustomComponent for VTK button to VTK extension

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2019-12-20 15:59:36 -05:00
ohif-bot a436436def chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.2
 - @ohif/ui@1.1.7
 - @ohif/viewer@3.3.2
2019-12-20 19:10:48 +00:00
Mirna Silva d6d92afbda test: Refactoring toolbar data-cy selectors (#1323) 2019-12-20 14:07:00 -05:00
ohif-bot e1142910e4 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.1
 - @ohif/ui@1.1.6
 - @ohif/viewer@3.3.1
2019-12-20 18:57:38 +00:00
Mirna Silva 067e6c769b test: Save Measurements Functionality (#1300)
* E2E Tests: Save Measurements Functionality

* Moving test file to PWA only. Script-tag doesn't have the required Study

* Removing data-cy selector from cornerstone toolbar
2019-12-20 13:54:59 -05:00
ohif-bot d375d02a4b chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.2.0
 - @ohif/extension-vtk@1.2.0
 - @ohif/core@2.3.0
 - @ohif/viewer@3.3.0
2019-12-20 17:18:56 +00:00
Igor OctavianoandDanny Brown 63594d36b0 feat: 🎸 Configuration so viewer tools can nix handles (#1304)
* feat: 🎸 Configuration so viewer tools can nix handles

Adds a key to cornerstone configuration that allows to toggle on/off
handle rendering

Closes: #1223

* Update extension docs

* Update doc

* docs: include hideHandles configuration in our configuration docs

* chore: default to hiding handles for annotations

* chore: switch to drawHandlesOnHover to mimic legacy viewer functionality

* Flip logic for drawHandlesOnHover; reverse of hideHandles

* Don't hide the handles of ellipse or rectangle roi

* invert check

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2019-12-20 12:15:40 -05:00
ohif-bot 6c160f517f chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.7
 - @ohif/ui@1.1.5
 - @ohif/viewer@3.2.2
2019-12-19 15:15:49 +00:00
Gustavo André Lelis 23305cec9c fix: 🐛 Fix drag-n-drop of local files into OHIF (#1319)
* fix: 🐛 Fix drag-n-drop of local files into OHIF

Closes: #1307

* CR Update: Centralize dialogs
2019-12-19 10:14:05 -05:00
ohif-bot 36a741f0bc chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.6
 - @ohif/core@2.2.1
 - @ohif/viewer@3.2.1
2019-12-18 18:29:28 +00:00
Igor Octaviano 695ad732e1 refactor: 💡 Move DICOMSR code to @ohif/core project (#1310)
* Move DICOMSR to @ohif/core

* refactor: 💡 Move DICOMSR code to @ohif/core project

We've just merged support for DICOM Structured Reports. Today, support
for this feature lives in the @ohif/viewer project in it's lib folder.
Ideally, it should be moved to @ohif/core and given a clear public API
and unit tests.

Closes: #1282

* Update imports

* CR Update: Update imports
2019-12-18 13:25:53 -05:00
ohif-bot 73a3f07e2b chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.5
 - @ohif/core@2.2.0
 - @ohif/viewer@3.2.0
2019-12-16 19:11:59 +00:00
Igor Octaviano 4ea239a953 feat: 🎸 Expose extension config to modules (#1279)
* feat: 🎸 Expose extension config to modules

Currently, only the preRegistration hook receives the extension's
configuration as a parameter. Providing getModuleFn's with the
extension's configuration, and all lifecycle/modules with the
application's configuration as rootConfig should open the doors to more
configurable extensions.

Closes: #1268

* CR Update: Pass extension and service config through extension manager preinit/getmodule

* CR Update: Remove appConfig from servicesManager

* CR Update: Remove appconfig variable
2019-12-16 14:07:53 -05:00
ohif-bot 625d5d70a7 chore(release): publish [skip ci]
- @ohif/viewer@3.1.12
2019-12-16 18:30:12 +00:00
Igor Octaviano 5c3d8b37b6 fix: 🐛 Dismiss all dialogs if leaving viewer route #1242 (#1301)
* fix: 🐛 #1242

Dismiss all dialogs if route change

Closes: #1242

* CR Update: Dismiss dialogs in videwer
2019-12-16 13:26:31 -05:00
ohif-bot 8bbbf3a889 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.1.1
 - @ohif/viewer@3.1.11
2019-12-16 16:49:50 +00:00
arthur-miller d5bf72851a fix: 🐛 add WwwcRegionTool to cornerstone tools initialization (#1302) 2019-12-16 11:47:02 -05:00
ohif-bot 2914c83032 chore(release): publish [skip ci]
- @ohif/extension-dicom-pdf@1.0.1
 - @ohif/viewer@3.1.10
2019-12-16 16:13:00 +00:00
Igor Octaviano 31e542dfd9 fix: 🐛 Update ohif pdf extension to optionally use pdfjs (#1162)
* fix: 🐛 Update ohif pdf extension to optionally use pdfjs

Update ohif pdf extension of optionally use pdfjs

Closes: #1049

* Update branch and fix scrolling

* Dynamically import component

* Fix wrong file change

* Fix import

* Add multi opened pdf configuration

* Refactor connected component

* Specify worker explicitly

* Specify worker explicitly

* CR Update: Remove dead code

* CR Update: Refactor state

* CR Update: use refs

* CR Update: use refs
2019-12-16 11:10:10 -05:00
ohif-bot 25599a2e2a chore(release): publish [skip ci]
- @ohif/i18n@0.52.4
 - @ohif/viewer@3.1.9
2019-12-16 15:13:14 +00:00
Danny Brown 1cf7015511 docs: stub environment variables docs 2019-12-16 09:47:12 -05:00
Igor Octaviano 756f70cc54 refactor: 💡 Use env variables to set locize (#1299)
Instead of relying in dynamic configuration, use dedicated env variables
to set locize api key / configuration

Closes: #1271
2019-12-16 09:41:41 -05:00
ohif-bot ded2e61913 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.4
 - @ohif/core@2.1.1
 - @ohif/ui@1.1.4
 - @ohif/viewer@3.1.8
2019-12-16 14:28:33 +00:00
Igor Octaviano b58aa4575a refactor: Remove LabelingFlow/ContextMenu services and reimplement on top of the Dialog service #1264 (#1292)
* Remove ContextMenu and Labelling services and provider to use DialogService directly

* Refactor init to display labelling

* Cleanup labelling flow in measurements init

* Cleanup viewer imports

* Add classname to toolcontextmenu

* Add error message
2019-12-16 09:19:40 -05:00
Danny Brown f78abe2afa docs: stub viewer configuration 2019-12-16 09:15:10 -05:00
Danny Brown f48157117f docs: hide empty pages 2019-12-16 09:00:24 -05:00
Danny Brown 6f8bd0dff8 docs: fix scope of diagram path 2019-12-16 08:57:43 -05:00
ohif-bot 943af89df2 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.3
 - @ohif/ui@1.1.3
 - @ohif/viewer@3.1.7
2019-12-13 15:14:42 +00:00
Danny Brown cd2da349e5 fix: allow empty values for dimensions (#1295)
* fix: allow empty values for dimensions

* fix: consistently use `defaultSize` for intial values
2019-12-13 10:09:18 -05:00
ohif-bot 14e86dcddd chore(release): publish [skip ci]
- @ohif/viewer@3.1.6
2019-12-13 04:45:56 +00:00
Danny Brown 2c8b93656d ci: fix webgl e2e test failures (only on CI) (#1296)
* ci: update executor and browser flag for circleci orbs

* ci: browser flag when running percy/ci tests

* ci: limit ourselves to the executors defined by the cypress orb for now
2019-12-12 23:43:22 -05:00
ohif-bot 6932468206 chore(release): publish [skip ci]
- @ohif/viewer@3.1.5
2019-12-13 02:19:44 +00:00
Danny Brown 7bc9efb0f5 ci: store cypress videos as artifacts; make sure we upload them to dashboard
* fix: Set VTK viewport as active by interaction

* chore: upgrade cypress to latest

* Revert "fix: Set VTK viewport as active by interaction"

This reverts commit b6e174c2def9d8f688a54d32053679788b9aaf76.

* ci: add record flag for master cypress runs

* ci: simplify cypress orb tasks now that cypress@3.8 uses electron w/ chromium 78

* ci: remove video false flag

* ci: drop browser flag

* ci: store video artifacts
2019-12-12 21:17:35 -05:00
ohif-bot 5d47cc43c1 chore(release): publish [skip ci]
- @ohif/viewer@3.1.4
2019-12-13 01:26:39 +00:00
Danny Brown e46a11a01e ci: upgrade cypress version (#1293)
* fix: Set VTK viewport as active by interaction

* chore: upgrade cypress to latest

* Revert "fix: Set VTK viewport as active by interaction"

This reverts commit b6e174c2def9d8f688a54d32053679788b9aaf76.

* ci: add record flag for master cypress runs
2019-12-12 20:21:27 -05:00
ohif-bot 27a950d956 chore(release): publish [skip ci]
- @ohif/viewer@3.1.3
2019-12-12 18:11:14 +00:00
Danny Brown 03d4c10adc chore: remove unused file 2019-12-12 13:08:30 -05:00
Mirna Silva 7a0c369006 test: PT/CT json, studies, series, frames, metadatas (#1145)
* Tests for routes: PT/CT json, studies, series, frames, metadatas

* Fix based on reviews and JSON file for PT/CT Study added

* chore: revert to remote JSON file

* chore: delete PTCTStudy.json file

We can circle back to this approach when/if we have issues. No need to hold-up this PR. My fault on this one.
2019-12-12 01:05:45 -05:00
ohif-bot 8c946a4742 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.2
 - @ohif/i18n@0.52.3
 - @ohif/ui@1.1.2
 - @ohif/viewer@3.1.2
2019-12-12 05:54:34 +00:00
Rodrigo Antinarelli 30b9e44220 fix: translations (#1234) 2019-12-12 00:46:58 -05:00
ohif-bot 380abe2705 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.1.1
 - @ohif/ui@1.1.1
 - @ohif/viewer@3.1.1
2019-12-11 16:48:26 +00:00
Mirna Silva f4570c96c2 tests: Download Image File Modal (#1219)
* E2E Tests: Download Image File Modal

* CI fix

* Fixes for PR comments

* Small change just to check CI behavior

* Fix for CI failure: comparing classes instead of image src

* Fixed Download button test and blocked Download Dialog event

* Deleting file ConnectedViewportDownloadForm.js

This file was not supposed to be created by this PR. It was already deleted by another user on Master branch.

* Removing wrong import line

* Fixes based on PR comments

* Fixes based on PR comments

* test: remove assignment to window
2019-12-11 11:46:10 -05:00
ohif-bot 1fe951a71e chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.1.0
 - @ohif/extension-dicom-html@1.1.0
 - @ohif/extension-vtk@1.1.0
 - @ohif/core@2.1.0
 - @ohif/ui@1.1.0
 - @ohif/viewer@3.1.0
2019-12-11 16:18:54 +00:00
Gustavo André Lelis ebe1af8d4f feat: 🎸 DICOM SR STOW on MeasurementAPI (#954)
* feat: 🎸 DICOM SR STOW on MeasurementAPI

Closes: #758

* feat: 🎸 Adding the dataExchance from DICOM SR

* feat: 🎸 Fix some issues and get the retrieve measurements working

* feat: 🎸 Preventing measurement tool helpers to broke because the data is not Number (it comes as string from dcmjs)

* feat: 🎸 Adding a button to start testing SATE functionality

* feat: 🎸Small fixes into the code

* feat: 🎸 MAke sure we get latest SR in the study, even on lazyLoad mode

* Revert "feat: 🎸 Preventing measurement tool helpers to broke because the data is not Number (it comes as string from dcmjs)"

This reverts commit 4b3a1efb5810487d2319826fd59c03d779882b5e.

* feat: 🎸 Make sure after sync we update the viewports and also that annotation tools are set to passive, so it can be drawn in the viewports

* feat: 🎸 Remove console logs and start using OHIF.log

* feat: 🎸 Refactor measurement table to use Snackbar service of messages and make sure we only show save button if server type is dicomWeb

* feat: 🎸 Refactor to remove mocked wadoRoot and use server

* Fix to expose error once we can't save the SR

* Update DCMJS into latest dev version

* Update dcmjs with latest fixes

* Fix PR callouts

* Logging error on MeasurementReport creation

* Preventing from breaking on non supported tools and also warning that some toolType is not sopported

* Small error message update

* Expose better the errors and when we have unsopported tools into the measurements

* Renaming variable

* Expose dcmjs tool support check

* Change the way we warn the unsupportedTools

* Adding style to the button

* Small change on button style

* Fix git conflicts

* Fix after merge with latest master

* Fix after merge with master, btn was not clickable

* Fixing callouts

* Adding util functions into files and refactor with JSDocs

* Refactoring code, adding some basic JSDocs

* Bump dcmjs with latest version and fixes

* JSDocs and small refactors

* yarn lock file after dcmjs update

* Remove unused parameters

* Registering a plain copy of server into measurementAPI

* Small refactor

* Start changing from toolType to toolName

* Rename a function to get instanceMetadata

* Adding error if server is not DICOMWeb on retrieve

* Move snackbar outside of UI component and use UINotificationService

* Refactor and JSDocs

* Small fixes on JSDocs (style)

* Remove unused import

* Improve JSDocs on MeasurementApi

* Remove refactor leftover

* Improve JSDocs and propTypes

* JSDocs and small refactors
2019-12-11 11:14:01 -05:00
ohif-bot 5cda5159da chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.0.2
 - @ohif/viewer@3.0.3
2019-12-11 15:41:29 +00:00
Danny Brown 115a3ec0b0 refactor: remove-labelling-manager
Igor/refactor/1267 remove labelling manager
2019-12-11 10:39:36 -05:00
Danny Brown ac82a111e7 Merge branch 'master' into igor/refactor/1267-remove-labelling-manager 2019-12-11 09:16:18 -05:00
Igor a22fd67e13 Fix config check in extension init 2019-12-11 06:54:41 -03:00
Danny Brown 91f0af058f Merge pull request #1278 from OHIF/dannyrb/ci/visual-test
ci: make sure we're updating the base branch for comparisons (percy)
2019-12-10 23:58:53 -05:00
dannyrb 5a1186388d Merge branch 'dannyrb/ci/visual-test' of github.com:OHIF/Viewers into dannyrb/ci/visual-test 2019-12-10 23:52:54 -05:00
dannyrb 2f0f92c095 ci: make sure we're updating the base branch for comparisons (percy) 2019-12-10 23:45:29 -05:00
Danny Brown 5c686268ab Merge pull request #1274 from OHIF/refactor/igor/1267-remove-labelling-manager
refactor: 💡 Remove LabellingManager and Cleanup LabellingFlow #1267
2019-12-10 23:24:14 -05:00
Danny Brown 974aabc9b2 Merge pull request #1276 from OHIF/dannyrb/ci/visual-test
ci: optional visual test step
2019-12-10 23:12:30 -05:00
Danny Brown 17e3b49b06 Merge branch 'master' into dannyrb/ci/visual-test 2019-12-10 23:08:09 -05:00
dannyrb e3c119489a cI: Better job name for percy 2019-12-10 23:07:54 -05:00
ohif-bot 17447baf44 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.0.2
 - @ohif/core@2.0.2
 - @ohif/viewer@3.0.2
2019-12-11 03:31:18 +00:00
Igor Octaviano daa3ddccdb refactor: 💡 ServicesManager factory functions vs instances (#1272)
* refactor: 💡 ServicesManager factory functions vs instances

Refactor servicesManager to take factory functions instead of instances

Closes: #1265

* Remove testing code
2019-12-10 22:28:23 -05:00
dannyrb c91b6dddd8 ci: optional visual test step 2019-12-10 22:17:21 -05:00
Igor 92548191e4 refactor: 💡 Remove LabellingManager and Clean LabellingFlow
Migrate logic from LabellingManager to LabellingFlow and cleanup
LabellingFlow component

Closes: #1267
2019-12-10 18:31:55 -03:00
Igor 68fcaef19a Cleanup labelling flow 2019-12-10 18:18:37 -03:00
Igor ef611e4849 Refactoring labellingflow to function component 2019-12-10 18:08:51 -03:00
Igor d128fd6bf0 Remove LabellingManager component 2019-12-10 17:41:43 -03:00
Igor 3bdd434488 Cleanup LabellingFlow and LabellingManager components 2019-12-10 17:29:40 -03:00
Danny Brown 29bac8df7a chore: don't mark CI/Tooling tasks as stale 2019-12-10 11:37:39 -05:00
Danny Brown 319c67c389 docs: swap out "always red" badge for viewer-dev.ohif.org badge 2019-12-10 08:57:03 -05:00
Danny Brown b9836f6c71 docs: templates in P_R_T directory can only be set w/ query string; removing 2019-12-09 22:06:01 -05:00
Danny Brown 6a6e2d7df3 docs: shift pr template up a dir 2019-12-09 22:05:05 -05:00
Danny Brown 2f8c408ed2 docs: contributing guidelines 2019-12-09 22:02:29 -05:00
Danny Brown 27910239e0 docs: code of conduct 2019-12-09 22:00:17 -05:00
ohif-bot 719bfd02eb chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.0.1
 - @ohif/extension-vtk@1.0.1
 - @ohif/core@2.0.1
 - @ohif/ui@1.0.1
 - @ohif/viewer@3.0.1
2019-12-09 18:58:37 +00:00
dannyrb 994818faeb Merge branch 'master' of github.com:OHIF/Viewers into ups-master 2019-12-09 13:55:04 -05:00
dannyrb f06ef53e83 docs: beginnings of SOPClassHandlerModule docs 2019-12-09 13:53:35 -05:00
ohif-bot c7008621e4 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.0.0
 - @ohif/extension-vtk@1.0.0
 - @ohif/core@2.0.0
 - @ohif/ui@1.0.0
 - @ohif/viewer@3.0.0
2019-12-09 17:49:51 +00:00
Igor Octaviano 55a580659e feat!: Ability to configure cornerstone tools via extension configuration (#1229)
* Fix ExtensionManager bug and add test to bandaid

* Add tools configuration to extension manager preinit

* Fix reducing of configs

* Merge internal with external configs

* Merge internal with external configs

* Remove dialog from init in measurementstable

* Testing injected configuration

* New way to set config

* Add new prop to dialog provider to allow disabling last position

* Remove code from preinit in cornerstone

* Add new prop to dialog provider to allow disabling last position

* Add centralize to dialogs

* Reorder dialogs when adding them

* Fix draggable styles (cursor)

* Remove repositioning methods from labelling flow and remove overlay from labelling manager

* Fix empty array being set in bringToFront

* Add new command to update table and pass commands manager to modules/preinit hook

* Ad UIContextMenu service / factory

* Use new contextmenu service in measurementspanel extension

* Use dialogs for arrow annotate in default

* Remove positioning funcionality from tool context menu

* Add context menu service

* Pass commandsModule to extension

* Update edit description dialog and simple dialog to position relative

* Remove style code from labelling flow and manager

* Remove eventdata from labelling

* Remove labelling code from measurement init

* Add commandsmanager to provider

* Update contextmenu provider and service

* Add touchstart and mouseclick to hide contextmenu

* Hide labelling if click/touch

* Remove labelling and context menu dead code

* Fix undefined bug if ViewerMain grid has no children

* Fix broken prop on context menu

* Update commandsmodule based on master

* Fix broken configuration

* Update script tag config

* Remove cornerstone from toolcontextmenu

* Remove cornerstone from toolcontextmenu

* Split labelling and context menu providers

* Split labelling and context menu providers

* Update test

* Destructure extensions into new array

* CR Update: Move default arrow config to cornerstone instead of default

* CR Update: Fix app configuration props structure

* CR Update: Fix app configuration prop in script tag and extract commands manager from providers

* CR Update: Create custom providers to use commandsManager

* CR Update: Use services directly in measurementspanel

* CR Update: Pass components to providers

* CR Update: Remove position from dialog

* CR Update: fix dialog prop check

* CR Update: Fix comments

* CR Update: Update documentation

* CR Update: Add test default configuration

* CR Update: Add default empy array to extensions

* CR Update: Update i18n configuration all ot match current function configuration

* CR Update: Add defaults to injected dependencies in configuration and extension configuration

* CR Update: Add defaults to configuration with no args

* Update documentation

* CR Update: Add default for tools

* CR Update: Update config object to i18n

* CR Update: spread defaults

* CR Update: Add tool configuration example to cornerstone extension

* CR Update: Add tool configuration to netlify (testing)

* CR Update: Remove netlify config for tools

* CR Update: Rollback changes to i18n to be fixed later

* CR Update: Update documentation and pass whole cornerstone config object instead of tools key

SEE: https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-both-and-breaking-change-footer

BREAKING CHANGE: modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
2019-12-09 12:47:23 -05:00
dannyrb fd7620ced9 docs: add command behaviors to disambiguate situations in which commands may behave unexpectedly 2019-12-09 10:15:06 -05:00
dannyrb cd1cbf0f85 docs: fix GitHub table formatting issue 2019-12-09 09:56:17 -05:00
dannyrb 08055398de docs: commands module docs 2019-12-09 09:52:13 -05:00
dannyrb f6db7fade6 docs: basic viewport docs 2019-12-09 01:02:02 -05:00
dannyrb 58d2bf1d12 test: consistent pre-amble for panel docs 2019-12-09 00:44:43 -05:00
dannyrb 87f5eca659 docs: gif of nested buttons 2019-12-09 00:44:26 -05:00
dannyrb 922ba8cc42 docs: toolbar docs for three different kinds of definitions 2019-12-09 00:44:04 -05:00
dannyrb 777ff88f3d docs: fix incorrect paths 2019-12-08 21:31:00 -05:00
dannyrb 68858db4b6 docs: fixing jsdoc for preregistration example 2019-12-08 21:26:54 -05:00
dannyrb ab106afae3 docs: panel docs and gif 2019-12-08 21:26:39 -05:00
dannyrb ac69b0a656 docs: ExtensionManager class docs 2019-12-08 14:37:54 -05:00
dannyrb 8edba24eec docs: filling in preRegistration hook info 2019-12-08 14:37:42 -05:00
dannyrb aae232fc3e docs: tidying up extensions summary info 2019-12-08 14:09:42 -05:00
ohif-bot 8fad8ceb80 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.54.6
 - @ohif/ui@0.65.4
 - @ohif/viewer@2.11.8
2019-12-07 06:23:21 +00:00
dannyrb f00f48da2b docs: fix broken links 2019-12-07 01:21:23 -05:00
dannyrb 076b89ef83 docs: stub bulk of missing extension docs 2019-12-07 01:20:09 -05:00
dannyrb 4920b527d1 docs: fix broken link 2019-12-07 01:13:33 -05:00
dannyrb 482041f053 docs: modules and contexts summary 2019-12-07 01:13:20 -05:00
dannyrb 674edf85f0 Merge branch 'master' of github.com:OHIF/Viewers into ups-master 2019-12-07 00:49:45 -05:00
dannyrb 33a1a465ea docs: lifecycle hooks summary 2019-12-07 00:49:40 -05:00
ohif-bot 6e04de30b6 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.54.5
 - @ohif/ui@0.65.3
 - @ohif/viewer@2.11.7
2019-12-07 05:27:27 +00:00
dannyrb 19117167ac Merge branch 'master' of github.com:OHIF/Viewers into ups-master 2019-12-07 00:25:31 -05:00
dannyrb 22e3c5d953 docs: prefixes in h1 titles to disambiguate pages 2019-12-07 00:23:43 -05:00
dannyrb 9a34877be4 docs: remove unused configuration link 2019-12-07 00:21:36 -05:00
dannyrb b1b66429b6 docs: stub quick start page 2019-12-07 00:19:49 -05:00
ohif-bot 78e522fbf6 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.54.4
 - @ohif/ui@0.65.2
 - @ohif/viewer@2.11.6
2019-12-07 05:14:07 +00:00
dannyrb 830cb82965 docs: shift files around to match summary structure 2019-12-07 00:12:08 -05:00
dannyrb 9a71edda6a docs: translating --> internationlization 2019-12-06 22:56:00 -05:00
dannyrb 7a8ae23839 docs: nuke troubleshooting.md 2019-12-06 22:50:26 -05:00
dannyrb 6873c4f065 docs: shift "getting started" outside of essentials 2019-12-06 22:48:57 -05:00
dannyrb 6ea16eccd9 docs: call out different config types 2019-12-06 22:22:46 -05:00
dannyrb ff381c6521 docs: shift viewer specific docs to own section 2019-12-06 22:21:52 -05:00
dannyrb 26cd695b8b docs: shift measurements to services directory 2019-12-06 21:58:13 -05:00
dannyrb a4f1b2195c docs: clean up links 2019-12-06 21:57:35 -05:00
dannyrb c8df17b032 docs: shift architecture to own dir; simplify sidebar 2019-12-06 21:54:15 -05:00
233 changed files with 7147 additions and 5323 deletions

No files matched your search

+62 -28
View File
@@ -13,14 +13,7 @@ version: 2.1
##
orbs:
codecov: codecov/codecov@1.0.5
cypress: cypress-io/cypress@1.11.0
executors:
# Custom executor to override Cypress config
deploy-to-prod-executor:
docker:
- image: 'cypress/browsers:node10.16.0-chrome77'
environment:
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
cypress: cypress-io/cypress@1.13.0
defaults: &defaults
docker:
@@ -335,12 +328,15 @@ workflows:
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
wait-on: 'http://localhost:3000'
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires:
- UNIT_TESTS
# E2E: script-tag
@@ -357,16 +353,46 @@ workflows:
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build:package
start: yarn run test:e2e:serve
spec: 'cypress/integration/common/**/*'
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
wait-on: 'http://localhost:3000'
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires:
- UNIT_TESTS
PR_OPTIONAL_VISUAL_TESTS:
jobs:
- AWAIT_APPROVAL:
type: approval
# Update hub.docker.org
- cypress/run:
name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
# start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
requires:
- AWAIT_APPROVAL
PR_OPTIONAL_DOCKER_PUBLISH:
jobs:
# https://circleci.com/docs/2.0/workflows/#holding-a-workflow-for-a-manual-approval
@@ -419,25 +445,33 @@ workflows:
filters:
branches:
only: master
# Update base branch snapshots
# and record a Cypress dashboard test run
- cypress/run:
name: 'Generate Percy Snapshots'
executor: cypress/browsers-chrome76
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
add wait-on' # Use yarn latest
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build
# start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace
post-steps:
- store_artifacts:
path: platform/viewer/cypress/screenshots
- store_artifacts:
path: platform/viewer/cypress/videos
- store_test_results:
path: platform/viewer/cypress/results
filters:
branches:
only: master
- DOCKER_MASTER_PUBLISH:
requires:
- NPM_PUBLISH
#
#
# Potentially use to E2E test STAGING environment
# before publishing to production
# - cypress/run:
# name: 'E2E: PWA'
# executor: deploy-to-prod-executor
# browser: chrome
# working_directory: platform/viewer
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
# yarn: true
# record: false
# no-workspace: true
# store_artifacts: true
# post-steps:
# - store_artifacts:
# path: platform/viewer/cypress/screenshots
# - store_test_results:
# path: cypress/results
+23 -9
View File
@@ -1,17 +1,31 @@
#!/bin/bash
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
if [ ! -z "$CLIENT_ID" ]
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
then
echo "Google Cloud Healthcare $CLIENT_ID has been provided: "
echo $CLIENT_ID
echo "Updating config..."
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
if [ -n "$CLIENT_ID" ]
then
echo "Google Cloud Healthcare \$CLIENT_ID has been provided: "
echo "$CLIENT_ID"
echo "Updating config..."
# - Use SED to replace the CLIENT_ID that is currently in google.js
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
# - Use SED to replace the CLIENT_ID that is currently in google.js
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
fi
# - Copy google.js to overwrite app-config.js
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
# If HEALTHCARE_API_ENDPOINT is specified, use the google.js configuration with the modified endpoint
if [ -n "$HEALTHCARE_API_ENDPOINT" ]
then
echo "Google Cloud Healthcare \$HEALTHCARE_API_ENDPOINT has been provided: "
echo "$HEALTHCARE_API_ENDPOINT"
echo "Updating config..."
# - Use SED to replace the HEALTHCARE_API_ENDPOINT that is currently in google.js
sed -i -e "s+https://healthcare.googleapis.com/v1beta1+$HEALTHCARE_API_ENDPOINT+g" /usr/share/nginx/html/google.js
fi
# - Copy google.js to overwrite app-config.js
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
fi
echo "Starting Nginx to serve the OHIF Viewer..."
+3 -2
View File
@@ -9,13 +9,14 @@ daysUntilClose: 7
exemptLabels:
- 'Story :raised_hands:'
- 'Bug: Verified :bug:'
- 'Task: CI/Tooling 🤖'
- 'Task: CI/Tooling :robot:'
- 'Task: Docs 📖'
- 'Task: Docs :book:'
- 'Task: Refactor 🛠'
- 'Task: Refactor :hammer_and_wrench:'
- 'Task: Tests :microscope:'
- 'PR: Awaiting Review 👀'
- 'Triage :white_flag:'
- 'Extension: Discussion'
- 'Announcement 🎉'
# Label to use when marking an issue as stale
staleLabel: 'Stale :baguette_bread:'
+1
View File
@@ -30,6 +30,7 @@ docker/dcm4che/dcm4che-arc
# Cypress test results
videos/
screenshots/
# Locize settings
.locize
+5
View File
@@ -72,12 +72,17 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
},
plugins: [
new webpack.DefinePlugin({
/* Application */
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
}),
],
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at danny.ri.brown+OHIFcoc@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+1
View File
@@ -0,0 +1 @@
See our contributing guidelines at [`https://docs.ohif.org`](https://docs.ohif.org/development/contributing.html)
+8 -9
View File
@@ -84,7 +84,7 @@ We offer support through
For commercial support, academic collaberations, and answers to common
questions; please read our
[documented FAQ](https://docs.ohif.org/frequently-asked-questions.html#does-ohif-offer-commercial-support).
[documented FAQ](https://docs.ohif.org/faq/index.html#does-ohif-offer-commercial-support).
## Quick Start Deployment
@@ -271,8 +271,8 @@ MIT © [OHIF](https://github.com/OHIF)
<!-- Badges -->
[lerna-image]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg
[lerna-url]: https://lerna.js.org/
[netlify-image]: https://api.netlify.com/api/v1/badges/a5d369ab-18a6-41c3-bcde-83805205ac7f/deploy-status
[netlify-url]: https://app.netlify.com/sites/ohif/deploys
[netlify-image]: https://api.netlify.com/api/v1/badges/32708787-c9b0-4634-b50f-7ca41952da77/deploy-status
[netlify-url]: https://app.netlify.com/sites/ohif-dev/deploys
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
[circleci-image]: https://circleci.com/gh/OHIF/Viewers.svg?style=svg
[circleci-url]: https://circleci.com/gh/OHIF/Viewers
@@ -296,14 +296,14 @@ MIT © [OHIF](https://github.com/OHIF)
[monorepo]: https://en.wikipedia.org/wiki/Monorepo
[how-to-fork]: https://help.github.com/en/articles/fork-a-repo
[how-to-clone]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork
[ohif-architecture]: https://docs.ohif.org/advanced/architecture.html
[ohif-extensions]: https://docs.ohif.org/advanced/architecture.html
[ohif-architecture]: https://docs.ohif.org/architecture/index.html
[ohif-extensions]: https://docs.ohif.org/architecture/index.html
[deployment-docs]: https://docs.ohif.org/deployment/
[react-url]: https://reactjs.org/
[pwa-url]: https://developers.google.com/web/progressive-web-apps/
[ohif-viewer-url]: https://www.npmjs.com/package/@ohif/viewer
[configuration-url]: https://docs.ohif.org/essentials/configuration.html
[extensions-url]: https://docs.ohif.org/advanced/extensions.html
[configuration-url]: https://docs.ohif.org/configuring/
[extensions-url]: https://docs.ohif.org/extensions/
<!-- Platform -->
[platform-core]: platform/core/README.md
[core-npm]: https://www.npmjs.com/package/@ohif/core
@@ -326,5 +326,4 @@ MIT © [OHIF](https://github.com/OHIF)
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
<!-- prettier-ignore-end -->
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
+2 -2
View File
@@ -38,9 +38,9 @@ our integration and extension points.
Check out these helpful links:
- Ready to dive into some code? Check out our
[Getting Started Guide](./essentials/getting-started.md).
[Getting Started Guide](./development/getting-started.md).
- We're an active, vibrant community.
[Learn how you can be more involved.](./contributing/index.md)
[Learn how you can be more involved.](./development/contributing.md)
- Feeling lost? Read our [help page](./help.md).
<!--
+18 -24
View File
@@ -1,26 +1,20 @@
# OHIF Viewers
- [Our Process](our-process.md)
- Essentials
- [Getting Started](essentials/getting-started.md)
- [Installation](essentials/installation.md)
- [Data Source](essentials/data-source.md)
- [Configuration](essentials/configuration.md)
- [Themeing](essentials/themeing.md)
- [Translating](essentials/translating.md)
- [Troubleshooting](essentials/troubleshooting.md)
- [Scope of Project](essentials/scope-of-project.md)
- Development
- [Getting Started](development/getting-started.md)
- [Contributing](development/contributing.md)
- [Continuous Integration](development/continous-integration.md)
- [Configuring](configuring/index.md)
- [Data Source](configuring/data-source.md)
---
- [Advanced](advanced/index.md)
- [Architecture](advanced/architecture.md)
- [Overview](advanced/architecture.md#overview)
- [Business Logic](advanced/architecture.md#business-logic)
- [Component Library](advanced/architecture.md#react-component-library)
- [Extensions](advanced/architecture.md#misc-extensions)
- [Diagram](advanced/architecture.md#diagram)
- [Common Questions](advanced/architecture.md#common-questions)
- [Architecture](architecture/index.md)
- [Viewer](viewer/index.md)
- [Configuration](viewer/configuration.md)
- [Themeing](viewer/themeing.md)
- [Internationalization](viewer/internationalization.md)
- [Extensions](extensions/index.md)
- [Registering](extensions/index.md#registering-an-extension)
- [Lifecycle Hooks](extensions/index.md#lifecycle-hooks)
@@ -31,15 +25,15 @@
- [SOP Class Handler](extensions/modules/sop-class-handler.md)
- [Toolbar](extensions/modules/toolbar.md)
- [Viewport](extensions/modules/viewport.md)
- [Contexts](extension/index.md#contexts)
- [OHIF Maintained](extension/index.md#maintained-extensions)
- [Contexts](extensions/index.md#contexts)
- [ExtensionManager](extensions/index.md#extensionmanager)
- [OHIF Maintained](extensions/index.md#maintained-extensions)
- [Services](services/index.md)
- [Default](services/default/index.md)
- [UI](services/ui/index.md)
- [Dialog Service](services/ui/ui-dialog-service.md)
- [Modal Service](services/ui/ui-modal-service.md)
- [Notification Service](services/ui/ui-notification-service.md)
- [Custom Tools](advanced/custom-tools.md)
---
@@ -59,8 +53,8 @@
---
- [FAQ](frequently-asked-questions.md)
- [Contributing](contributing/index.md)
- [Continuous Integration](continous-integration.md)
- [Browser Support](browser-support.md)
- [FAQ](faq/index.md)
- [Scope of Project](faq/scope-of-project.md)
- [Browser Support](faq/browser-support.md)
- [PWA vs Packaged](faq/pwa-vs-packaged.md)
- [Help](help.md)
@@ -1,62 +0,0 @@
<table>
<thead>
<tr>
<th>Extension</th>
<th>Description</th>
<th>Modules</th>
</tr>
</thead>
<tbody>
<!-- CORNERSTONE.js -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-cornerstone">
Cornerstone
</a>
</td>
<td>
A viewport powered by <code>cornerstone.js</code>. Adds support for 2D DICOM rendering and manipulation, as well as support for the tools features in <a href="https://tools.cornerstonejs.org/examples/"><code>cornerstone-tools</code></a>. Also adds "CINE Dialog" to the Toolbar.
</td>
<td>Viewport, Toolbar</td>
</tr>
<!-- VTK.js -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-vtk">
VTK.js
</a>
</td>
<td>
A viewport powered by <code>vtk.js</code>. Adds support for volume renderings and advanced features like MPR. Also adds "3D Rotate" to the Toolbar.
</td>
<td>Viewport, Toolbar</td>
</tr>
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-html">DICOM HTML</a>
</td>
<td>
Renders text and HTML content for <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12">specific SopClassUIDs</a>.
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-pdf">DICOM PDF</a>
</td>
<td>
Renders PDFs for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6">specific SopClassUID</a>.
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-microscopy">DICOM Microscopy</a>
</td>
<td>
Renders Microscopy images for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7">specific SopClassUID</a>.
</td>
<td>Viewport, SopClassHandler</td>
</tr>
</tbody>
</table>
-9
View File
@@ -1,9 +0,0 @@
# Tool Management
This is not yet exposed in an easy/convenient way. Most tools are currently
added by creating new Viewport, Toolbar, and SOPInstanceHandler extension
modules. You can read more about that approach in
[extensions](../extensions/index.md).
In the near future, we intend to improve the extensibility of tools for existing
Viewports (like our Cornerstone.js and VTK.js viewports).
-3
View File
@@ -1,3 +0,0 @@
# Advanced
Advanced topics go beyond basic configuration and deployment. Their goal is to provide insight into this project's architecture and guidance on leveraging extensions.
@@ -1,23 +0,0 @@
# MonoRepos: A Crash Course
- [Lerna][lerna]
Solutions:
## Semantic-Release
- [Semantic-Release](https://github.com/semantic-release/semantic-release/issues/193#issuecomment-462063871)
- [Multi-semantic-release](https://github.com/dhoulb/multi-semantic-release)
- [semantic-release-monorepo](https://github.com/Updater/semantic-release-monorepo)
# Netlify
- https://community.netlify.com/t/best-practices-for-deploying-sites-from-monorepos/818
<!--
Links
-->
<!-- prettier-ignore-start -->
[lerna]: https://github.com/lerna/lerna
<!-- prettier-ignore-end -->
@@ -10,6 +10,13 @@ allows our code to be used in more ways, it also increases complexity. Below, we
aim to demistify that complexity by providing insight into how our Viewer is
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)
## Overview
The [OHIF Medical Image Viewing Platform][viewers-project] is maintained as a
@@ -39,27 +46,47 @@ you'll see the following:
└── README.md
```
Continue reading to see how these libraries work together to create the OHIF
Viewer.
The `platform` directory contains the business logic library, component library,
and the application library that combines them to create a powerful medical
imaging viewer.
### Business Logic
The `extensions` directory contains many packages that can be registered with
`@ohif/core`'s `ExtensionManager` to expand an application's supported features
and functionality.
![Architecture Diagram](../assets/img/architecture-diagram.png)
<center><i>architecture diagram</i></center>
This diagram is a conceptual illustration of how the Viewer is architected.
1. (optional) `extensions` can be registered with `@ohif/core`'s
`ExtensionManager`
2. `@ohif/core` provides bussiness logic and a way for `@ohif/viewer` to access
registered extensions
3. The `@ohif/viewer` composes and provides data to components from our
component library (`@ohif/ui`)
4. The `@ohif/viewer` can be built and served as a stand-alone PWA, or as an
embeddable package ([`@ohif/viewer`][viewer-npm])
## Business Logic
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
- [Managing a study's measurements](../services/default/measurements.md)
- Managing a study's DICOM metadata
- A flexible pattern for extensions
- [A flexible pattern for extensions](../extensions/index.md)
- And many others
It does this while remaining decoupled from any particular view library or
rendering logic. While we use it to power our React Viewer, it can be used with
Vue, React, Vanilla JS, or any number of other frameworks.
### React Component Library
## React Component Library
[`@ohif/ui`][ui-github] is a React Component library that contains the reusable
components that power the OHIF Viewer. It allows us to build, compose, and test
@@ -72,15 +99,11 @@ application.
[Check out our component library!](https://react.ohif.org/)
### Internationalization (i18n)
## Internationalization (i18n)
...
### The Viewer
...
### Extensions & Configuration
## Extensions & Configuration
While OHIF maintains several high value and commonly requested features in its
own extensions, there are many instances where one may wish to further extend
@@ -105,23 +128,6 @@ looking for ways to improve our extensibility ^\_^
[Click here to read more about extensions!](../extensions/index.md)
### Diagram
This diagram is a conceptual illustration of how the Viewer is architected.
1. (optional) `extensions` can be registered with `@ohif/core`'s extension
manager
2. `@ohif/core` provides bussiness logic and a way for `@ohif/viewer` to access
registered extensions
3. The `@ohif/viewer` composes and provides data to components from our
component library (`@ohif/ui`)
4. The `@ohif/viewer` can be built and served as a stand-alone PWA, or as an
embeddable package ([`@ohif/viewer`][viewer-npm])
![Architecture Diagram](../assets/img/architecture-diagram.png)
<center><i>architecture diagram</i></center>
## Common Questions
> When should I use the packaged source `@ohif/viewer` versus building a PWA
@@ -141,10 +147,11 @@ you would need to recreate.
-->
<!-- prettier-ignore-start -->
[monorepo]: #
[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]: ../essentials/configuration.md
[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
Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

@@ -1,8 +1,9 @@
# Data Source
After following the steps outlined in [Getting Started](./getting-started.md),
you'll notice that the OHIF Viewer has data for several studies and their
images. You didn't add this data, so where is it coming from?
After following the steps outlined in
[Getting Started](./../development/getting-started.md), you'll notice that the
OHIF Viewer has data for several studies and their images. You didn't add this
data, so where is it coming from?
By default, the viewer is configured to connect to a remote server hosted by the
nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started,
@@ -84,7 +85,7 @@ yarn run dev:orthanc
#### Configuration: Learn More
> For more configuration fun, check out the
> [Essentials Configuration](./configuration.md) guide.
> [Essentials Configuration](./index.md) guide.
Let's take a look at what's going on under the hood here. `yarn run dev:orthanc`
is running the `dev:orthanc` script in our project's `package.json`. That script
@@ -130,7 +131,7 @@ window.config = {
```
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](./configuration.md).
[Configuration Guide](./index.md).
## Open Source DICOM Image Archives
@@ -2,7 +2,12 @@
> This step assumes you have an imaging archive. If you need assistance setting
> one up, check out the [`Data Source` Guide](./data-source.md) or a deployment
> recipe that contains an open source Image Archive
> recipe that contains an open Image Archive
- [Overview](#overview)
- [Configuration Files](#configuration-files)
- [Environment Variables](#environment-variables)
- [How do I configure my project?](#how-do-i-configure-my-project)
## Overview
@@ -16,8 +21,8 @@ and registered extension's features, are configured using this file.
**Embedded Use Note:**
Alternatively, when using the `commonjs` bundle for embedded use cases, these
same values are what you'll pass to `installViewer` method:
Alternatively, when using the `umd` bundle for embedded use cases, these same
values are what you'll pass to `installViewer` method:
`OHIFStandaloneViewer.installViewer(window.config)`
@@ -36,10 +41,6 @@ alternative data source (or even specify different default hotkeys).
| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` |
| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` |
### Registering Extensions
> :warn: Instructions coming soon
## How do I configure my project?
The simplest way is to update the existing default config:
@@ -65,6 +66,39 @@ window.config = {
};
```
The configuration can also be written as a JS Function in case you need to inject dependencies like external services:
```js
window.config = ({ servicesManager } = {}) => {
const { UIDialogService } = servicesManager.services;
return {
cornerstoneExtensionConfig: {
tools: {
ArrowAnnotate: {
configuration: {
getTextCallback: (callback, eventDetails) => UIDialogService.create({...
}
}
},
},
routerBasename: '/',
servers: {
dicomWeb: [
{
name: 'DCM4CHEE',
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
},
],
},
};
};
```
You can also create a new config file and specify its path relative to the build
output's root by setting the `APP_CONFIG` environment variable. You can set the
value of this environment variable a few different ways:
+6 -6
View File
@@ -10,9 +10,9 @@ your Viewer will display).
## Overview
Our goal is to make deployment as simple and painless as possible; however,
there is an inherent amount of complexity in customizing, optimizing, and
deploying web applications. If you find yourself a little lost, please don't
hesitate to [reach out for help](/help.md)
there is an inherent amount of complexity in configuring and deploying web
applications. If you find yourself a little lost, please don't hesitate to
[reach out for help](/help.md)
## Deployment Scenarios
@@ -96,12 +96,12 @@ support it yet, but it is gaining wider adoption.
If you have an existing archive and intend to host the OHIF Viewer at the same
domain name as your archive, then connecting the two is as simple as following
the steps layed out in our
[Configuration Essentials Guide](./../essentials/configuration.md).
[Configuration Essentials Guide](./../configuring/index.md).
#### What if I don't have an imaging archive?
We provide some guidance on configuring a local image archive in our
[Data Source Essentials](./../essentials/data-source.md) guide. Hosting an
[Data Source Essentials](./../configuring/data-source.md) guide. Hosting an
archive remotely is a little trickier. You can check out some of our
[advanced recipes](#recipes) for modeled setups that may work for you.
@@ -142,7 +142,7 @@ The OHIF Viewer can be configured to work with authorization servers that
support one or more of the OpenID-Connect authorization flows. The Viewer finds
it's OpenID-Connect settings on the `oidc` configuration key. You can set these
values following the instructions laid out in the
[Configuration Essentials Guide](./../essentials/configuration.md).
[Configuration Essentials Guide](./../configuring/index.md).
_Example OpenID-Connect Settings:_
@@ -1,7 +1,7 @@
# Build for Production
> If you've already followed the
> ["Getting Started" Guide](/essentials/getting-started.md), you can skip ahead
> ["Getting Started" Guide](/development/getting-started.md), you can skip ahead
> to [Configuration](#configuration)
## Overview
@@ -73,7 +73,7 @@ and registered extension's features, are configured using this file.
The easiest way to apply your own configuration is to modify the `default.js`
file. For more advanced cofiguration options, check out our
[configuration essentials guide](/essentials/configuration.md).
[configuration essentials guide](/configuring/index.md).
## Next Steps
@@ -24,12 +24,12 @@ include tags. Here's how it works:
</ul>
<ol start="2">
<li>Create a JS Object to hold the OHIF Viewer's configuration. Here are some
<li>Create a JS Object or Function to hold the OHIF Viewer's configuration. Here are some
example values that would allow the viewer to hit our public PACS:</li>
</ol>
```js
// Set before importing `ohif-viewer`
// Set before importing `ohif-viewer` (JS Object)
window.config = {
// default: '/'
routerBasename: '/',
@@ -49,6 +49,9 @@ window.config = {
};
```
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](./index.md).
<ol start="3"><li>
Render the viewer in the web page's target <code>div</code>
</li></ol>
@@ -7,8 +7,8 @@ like to discuss the changes you intend to make to clarify where or how they
should be implemented, please don't hesitate to create a new issue. At a
minimum, you may want to read the following documentation:
- [Essentials: Getting Started](/essentials/getting-started.md)
- [Advanced: Architecture](/advanced/architecture.md)
- [Getting Started](/development/getting-started.md)
- [Architecture](/architecture/index.md)
Pull requests that are:
@@ -85,20 +85,12 @@ yarn run build
yarn run build:package
```
## Next Steps
Take a look at the sidebar. Depending on how you intend to use The OHIF Viewer,
different topics may be more pertinent than others. At a minimum, reading "The
Essentials" and the deployment recipes should be a good starting point.
## Troubleshooting
- If you receive a _"No Studies Found"_ message and do not see your studies, try
changing the Study Date filters to a wider range.
- If you see a 'Loading' message which never resolves, check your browser
JavaScript console inside the Developer Tools to identify any errors.
- If you see any errors in your server console, check the
[Troubleshooting](./troubleshooting.md) page for more in depth advice.
<!--
Links
File renamed without changes.
-20
View File
@@ -1,20 +0,0 @@
# Troubleshooting
Common GitHub issues that are not easily remedied with cleaner code or
documentation will be recorded here. Please feel free to make PRs to update this
page.
## Common Problems
| Problem | Most Common Reasons |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ** Can't retrieve Study List over DICOMWeb** | 1. QIDO root URL is incorrect<br> 2. DICOM Web is not enabled on PACS |
| ** Can't retrieve images** | 1. WADO Root URL is incorrect<br> 2. DICOM Web is not enabled on PACS<br> 3. HTTP Basic Authentication username and password are incorrect or not provided. |
## Debugging Steps
### Can't retrieve Study List over DICOMWeb
1. Check that you can query your PACS using an alternative DICOM Web client
(e.g. cURL, or a Web Browser). If you cannot, then your PACS is configured
incorrectly. Refer to the documentation of the image archive.
+110 -24
View File
@@ -8,12 +8,15 @@
- [Modules](#modules)
- [Contexts](#contexts)
- [Consuming Extensions](#consuming-extensions)
- [Extension Manager](#extensionmanager)
- [Maintained Extensions](#maintained-extensions)
## Overview
We use extensions to help us isolate and package groups of related features.
Extensions provide functionality, ui components, and new behaviors.
Extensions provide functionality, ui components, and new behaviors. Ideally,
they're built in a way that allows them to extend entirely different
implementations of the `@ohif/viewer` project.
<div style="text-align: center;">
<a href="/assets/img/extensions-diagram.png">
@@ -46,9 +49,8 @@ Practical examples of extensions include:
### Extension Skeleton
An extension is a plain JavaScript object has an `id` property, and one or more
"getModuleFunctions" and/or lifecycle hooks. You can read more about
[lifecycle hooks](#lifecycle-hooks) and [modules](#modules) further down.
An extension is a plain JavaScript object that has an `id` property, and one or
more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks).
```js
// prettier-ignore
@@ -71,11 +73,19 @@ export default {
### Registering an Extension
There are two different ways to register and configure extensions. You can
leverage one or both strategies. Which one(s) you choose depend on your
There are two different ways to register and configure extensions: At
[runtime](#runtime-extensions) and at [build time](#bundled-extensions).
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
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.
```js
// prettier-ignore
const config = {
@@ -89,20 +99,13 @@ const config = {
}
```
#### Runtime Extensions
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.
#### Bundled Extensions
#### Registering at Build Time
The `@ohif/viewer` works best when built as a "Progressive Web Application"
(PWA). If you know the extensions your application will need, you can specify
them at "build time" to leverage some advantaged afforded to us by modern
tooling:
them at "build time" to leverage advantages afforded to us by modern tooling:
- Code Splitting
- Code Splitting (dynamic imports)
- Tree Shaking
- Dependency deduplication
@@ -114,23 +117,106 @@ You can update the list of bundled extensions by:
### Lifecycle Hooks
...
Currently, there is only a single lifecycle hook for extensions:
[`preRegistration`](./lifecycle/pre-registration.md)
If an extension defines the [`preRegistration`](./lifecycle/pre-registration.md)
lifecycle hook, it is called before any modules are registered in the
`ExtensionManager`. It's most commonly used to wire up extensions to
[services](./../services/index.md) and [commands](./modules/commands.md), and to
bootstrap 3rd party libraries.
### Modules
There are a few different module types. Each module type allows us to extend the
viewer in a different way, and provides a consistent API for us to do so. You
can find a full list of the different types of modules
[`in ohif-core`][module-types]. Information on each type of module, it's API,
and how we determine when/where it should be used is included below.
Modules are the meat of extensions. They provide "definitions", components, and
filtering/mapping logic that are then made available by various managers and
services.
Each module type has a special purpose, and is consumed by our viewer
differently.
| Type | Description | Examples |
| ------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
| [Commands](./modules/commands.md) | Adds named commands, scoped to a context, to the CommandsManager | `setToolActive()`, `nextSeries()` |
| [Panel](./modules/panel.md) | Adds left or right hand side panels | `<ThumbnailList />`, `<MeasurementsTable />` |
| [SOPClassHandler](./modules/sop-class-handler.md) | Determines how retrieved study data is split into "DisplaySets" | `getDisplaySetFromSeries()` |
| [Toolbar](./modules/toolbar.md) | Adds buttons or custom components to the toolbar | Toolbar button, nested buttons, custom |
| [Viewport](./modules/viewport.md) | Adds a component responsible for rendering a "DisplaySet" | `<CornerstoneViewport />`, `<DicomPdfViewport />` |
<figure style="text-align: center; font-style: italic;">Tbl. Module types with abridged descriptions and examples. Each module links to a dedicated documentation page.</figure>
### Contexts
...
The `@ohif/viewer` tracks "active contexts" that extensions can use to scope
their functionality. Some example contexts being:
- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST`
- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK`
An extension module can use these to say "Only show this Toolbar Button if the
active viewport is a Cornerstone viewport." This helps us use the appropriate UI
and behaviors depending on the current contexts.
For example, if we have hotkey that "rotates the active viewport", each Viewport
module that supports this behavior can add a command with the same name, scoped
to the appropriate context. When the `command` is fired, the "active contexts"
are used to determine the appropriate implementation of the rotate behavior.
## Consuming Extensions
...
We consume extensions, via the `ExtensionManager`, in our `@ohif/viewer`
project.
```js
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
});
// prettier-ignore
extensionManager.registerExtensions([ /** **/ ]);
```
The `@ohif/viewer` project handles data fetching, basic routing, wires up UI
services, and is the home to the more bespoke application logic that doesn't
make as much sense to make reusable.
Long-term, replacing the `@ohif/viewer` application and consuming extensions
(and the `ExtensionManager`) in your own project is the ideal path for
applications requiring a high degree of customization that can't be achieved
with current theming, configuration, extension, and services support.
If you're not sure how to achieve your goals with the extensibility available
today, create a GitHub issue!
### `ExtensionManager`
The `ExtensionManager` is a class made available to us via the `@ohif/core`
project (platform/core). Our application instantiates a single instance of it,
and provides a `ServicesManager` and `CommandsManager` along with the application's
configuration through the appConfig key (optional).
```js
const commandsManager = new CommandsManager();
const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
appConfig
});
```
The `ExtensionManager` only has a few public members:
- `registerExtension` - Registers a single extension
- `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.
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
appropriate places in our application.
## Maintained Extensions
@@ -1,12 +1,31 @@
# Lifecylce Hook: preRegistration
If an extension defines the `preRegistration` lifecycle hook, it is called
before any modules are registered to the `ExtensionManager`.
before any modules are registered in the `ExtensionManager`. This hook can be
used to:
- initialize 3rd party libraries
- register event listeners
- add or call services
- add or call commands
The `preRegistration` hook receives an object containing the
`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any
`configuration` that was provided with the extension at time of registration.
_Example `preRegistration` hook implementation_
```js
export default {
id: 'MyExampleExtension',
/**
* @param {object} params
* @param {object} params.configuration
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
* @returns void
*/
preRegistration({ servicesManager, commandsManager, configuration }) {
console.log('Wiring up important stuff.');
+151 -27
View File
@@ -1,34 +1,158 @@
# Module: Commands
The Commands Module allows us to register one or more commands scoped to
specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#],
and any registered custom react component (like a [viewport][#] or [panel][#]).
- [Overview](#overview)
- [Command Definitions](#command-definitions)
- [Commands Manager](#commands-manager)
- [Instantiating](#instatiating)
- [Public API](#public-api)
- [Contexts](#contexts)
## Overview
An extension can register a Commands Module by defining a `getCommandsModule`
method. The Commands Module allows us to register one or more commands scoped to
specific [contexts](./../index.md#contexts). Commands have several unique
characteristics that make them tremendously powerful:
- Multiple implementations for the same command can be defined
- Only the correct command's implementation will be run, dependent on the
application's "context"
- Commands can be called from extensions, modules, and the consuming application
Here is a simple example commands module:
```js
{
getCommandsModule() {
return {
actions: {
speak: ({ viewports, words }) => {
console.log(viewports, words);
},
},
definitions: {
rotateViewportCW: {
commandFn: actions.rotateViewport,
storeContexts: ['viewports'],
options: { rotation: 90 }
},
rotateViewportCCW: {
commandFn: actions.rotateViewport,
storeContexts: ['viewports'],
options: { rotation: -90 },
context: 'ACTIVE_VIEWER::CORNERSTONE'
},
},
defaultContext: 'VIEWER'
}
}
export default {
id: 'example-commands-module',
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
*/
getCommandsModule({ servicesManager, commandsManager }) {
return {
definitions: {
sayHello: {
commandFn: ({ words }) => {
console.log(words);
},
options: { words: 'Hello!' },
},
},
defaultContext: 'VIEWER',
};
},
};
```
Each definition returned by the Commands Module is registered to the
`ExtensionManager`'s `CommandsManager`.
## Command Definitions
The command definition consists of a named command (`myCommandName` below) and a
`commandFn`. The command name is used to call the command, and the `commandFn`
is the "command" that is actioned.
```js
myCommandName: {
commandFn: ({ viewports, other, options }) => { },
storeContexts: ['viewports'],
options: { words: 'Just kidding! Goodbye!' },
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
}
```
| Property | Type | Description |
| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. |
| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. |
| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` |
| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. |
## Command Behavior
**I have many similar commands. How can I share their `commandFn` and make it
reusable?**
This is where `storeContexts` and `options` come in. We use these in our
`setToolActive` command. `storeContexts` helps us identify our `activeViewport`,
and `options` allow us to pass in the name of a tool we would like to set as
active.
**If there are multiple valid commands for the application's active contexts**
- What happens: all commands are run
- When to use: A `clearData` command that cleans up state for multiple
extensions
**If no commands are valid for the application's active contexts**
- What happens: a warning is printed to the console
- When to use: a `hotkey` (like "invert") that doesn't make sense for the
current viewport (PDF or HTML)
## `CommandsManager`
The `CommandsManager` is a class defined in the `@ohif/core` project. A single
instance of it should be defined in the consuming application, and it should be
used when constructing the `ExtensionManager`.
### Instantiating
When we instantiate the `CommandsManager`, we need to pass it two methods:
- `getAppState` - Should return the application's state when called
- `getActiveContexts` - Should return the application's active contexts when
called
These methods are used internally to help determine which commands are currently
valid, and how to provide them with any state they may need at the time they are
called.
```js
const commandsManager = new CommandsManager({
getAppState,
getActiveContexts,
});
```
### Public API
If you would like to run a command in the consuming app or an extension, you can
use one of the following methods:
```js
// Returns all commands for a given context
commandsManager.getContext('string');
// Attempts to run a command
commandsManager.runCommand('speak', { command: 'hello' });
// Run command, but override the active contexts
commandsManager.runCommand('speak', { command: 'hello' }, ['VIEWER']);
```
The `ExtensionManager` handles registering commands and creating contexts, so
most consumer's won't need these methods. If you find yourself using these, ask
yourself "why can't I register these commands via an extension?"
```js
// Used by the `ExtensionManager` to register new commands
commandsManager.registerCommand('context', 'name', commandDefinition);
// Creates a new context; clears the context if it already exists
commandsManager.createContext('string');
```
### Contexts
It is up to the consuming application to define what contexts are possible, and
which ones are currently active. As extensions depend heavily on these, we will
likely publish guidance around creating contexts, and ways to override extension
defined contexts in the near future. If you would like to discuss potential
changes to how contexts work, please don't hesistate to createa new GitHub
issue.
[Some additional information on Contexts can be found here.](./../index.md#contexts)
+55 -1
View File
@@ -1,3 +1,57 @@
# Module: Panel
...
An extension can register a Panel Module by defining a `getPanelModule` method.
The panel module provides the ability to define `menuOptions` and `components`
that can be used by the consuming application. `components` are React Components
that can be displayed in the consuming application's "Panel" Component.
![Panel Extension](../../assets/img/extensions-panel.gif)
<center><i>A panel extension example</i></center>
The `menuOptions`'s `target` key points to a registered `components`'s `id`. A
`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can
optional provide it's own `context` value.
The `getPanelModule` receives an object containing the `ExtensionManager`'s
associated `ServicesManager` and `CommandsManager`.
```js
import MyComponent from './MyComponent.js';
export default {
id: 'example-panel-module',
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
*/
getPanelModule({ servicesManager, commandsManager }) {
return {
menuOptions: [
{
// A suggested icon
// Available icons determined by consuming app
icon: 'list',
// A suggested label
label: 'Magic',
// 'right' or 'left'
from: 'right',
// The target component to toggle open/close
target: 'target-component-id',
// Overrides `defaultContext`, if specified
context: ['ACTIVE_VIEWPORT:MAGIC'],
},
],
components: [
{
id: 'target-component-id',
component: MyComponent,
},
],
defaultContext: ['ROUTE:VIEWER'],
};
},
};
```
@@ -1,3 +1,90 @@
# 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:
- `sopClassUids`: an array of string SOP Class UIDs that the
`getDisplaySetFromSeries` method should be applied to.
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
display set
A `displaySet` has the following shape:
```js
return {
plugin: 'html',
modality: 'SR',
displaySetInstanceUid: 0,
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
sopInstanceUid: instance.getSOPInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(),
studyInstanceUid: study.getStudyInstanceUID(),
authorizationHeaders,
};
```
Where the `plugin` key is used to influence the default `ViewportComponent` for
rendering the `displaySet`. Additional properties are passed to the
`ViewportComponent` and used by the default `StudyBrowser` to render
"thumbnails" for each `displaySet`
## Example SOP Class Handler Module
```js
const SOP_CLASS_UIDS = {
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
};
export default {
id: 'example-sop-class-handler-module',
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
*/
getSopClassHandlerModule({ servicesManager, commandsManager }) {
return {
id: 'OHIFDicomHtmlSopClassHandler',
sopClassUids: Object.values(SOP_CLASS_UIDS),
/**
* @param {object} series -
* @param {object} study -
* @param {object} dicomWebClient -
* @param {object} authorizationHeaders -
*/
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
return {
plugin: 'html',
displaySetInstanceUid: 0,
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
sopInstanceUid: instance.getSOPInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(),
studyInstanceUid: study.getStudyInstanceUID(),
authorizationHeaders,
};
},
}
};
```
## `@ohif/viewer` usage
We use the `sopClassHandlerModule`s in three different places:
- `ViewerLocalFileData.js`
- `ViewerRetrieveStudyData.js`
- `StandaloneRouting.js`
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`.
+116 -34
View File
@@ -1,48 +1,130 @@
# Module: Toolbar
An extension can register a Toolbar Module by providing a `getToolbarModule()`
method that returns a React Component. The component does not receive any props.
If you want to modify or react to state, you will need to connect to the redux
store. The given toolbar must determine its set of elements and the context of
them. The set of elements will be listed on toolbar `definitions`.
An extension can register a Toolbar Module by defining a `getToolbarModule`
method. This module is commonly used to define:
![Toolbar Extension](../assets/img/extensions-toolbar.gif)
- [Toolbar buttons](#button-definitions)
- [Nested toolbar menus](#nested-toolbar-menus)
- [Custom components](#custom-components)
<center><i>A toolbar extension example</i></center>
![Toolbar Extension](../../assets/img/extensions-toolbar.gif)
Toolbar components are rendered in the `ToolbarRow` component.
<center><i>Example toolbar button using the Dialog Service to show CINE controls.</i></center>
For a complete example implementation,
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
## Example Toolbar Module
## Toolbar Custom Component
Toolbar elements can define its own custom react component to be consumed when
rendering it. So far, it accepts `Functional` and `Class` Components. For that,
you just need to expose your `CustomToolbarComponent` as the value of key
`CustomComponent`. In case the property `CustomComponent` is not present, a
default toolbar component will be used to render it. See bellow
The Toolbar Module should return an array of `definitions` and a
`defaultContext`. There are currently a few different variations of definitions,
each one is detailed further down.
```js
definitions: [
...
export default {
id: 'example-toolbar-module',
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
*/
getToolbarModule({ servicesManager, commandsManager }) {
return {
definitions: [
/* Array of definitions */
],
defaultContext: ['ROUTE:VIEWER'],
};
},
};
```
## Button Definitions
The simplest definition has the following properties:
```js
{
id: 'StackScroll',
label: 'Stack Scroll',
icon: 'bars',
type: 'setToolActive',
commandName: 'setToolActive',
commandOptions: { toolName: 'StackScroll' },
},
```
| property | description | values |
| ---------------- | ----------------------------------------------------------------- | ----------------------------------------- |
| `id` | Unique string identifier for the definition | \* |
| `label` | User/display friendly to show in UI | \* |
| `icon` | A string name for an icon supported by the consuming application. | \* |
| `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` |
| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` |
| `commandOptions` | (optional) Options to pass the target `commandName` | \* |
| `context` | (optional) Overrides module's `defaultContext` | Array of string context names |
Where a button with a `type` of `setToolActive` has an "active" styling applied
when clicked; removing the active styling from all other buttons.
## Nested Toolbar Menus
You can indicate that buttons should be grouped and nested in a submenu by
including `buttons` property in a definition:
```js
{
id: 'More',
label: 'More',
icon: 'ellipse-circle',
buttons: [
{
id: 'Custom',
label: 'Custom',
icon: 'custom-icon',
CustomComponent: CustomToolbarComponent,
}
...
]
id: 'cstInvert',
label: 'Invert',
icon: 'circle',
type: 'command',
commandName: 'invertViewport',
},
],
},
```
![Toolbar Extension](../../assets/img/extensions-toolbar-nested.gif)
<center><i>Example toolbar button demonstrating nested buttons.</i></center>
## Custom Components
The Toolbar Modules supports rendering custom components in place of the
application's default. In place of the `type`, `commandName`, and
`commandOptions` properties, we instead specify a `CustomComponent`.
```js
{
id: 'Custom',
label: 'Custom',
icon: 'custom-icon',
CustomComponent: CustomToolbarComponent,
}
```
`CustomComponent` components will receive the following props:
The `CustomComponent` components will receive the following props:
- parentContext: parent context. (In most of the cases it will be a ToolbarRow
instance)
- toolbarClickCallback: callback method when clicking on toolbar
- button: its own definition object
- key: react key prop
- activeButtons: list of active elements
- isActive: if current
```html
<CustomComponent
parentContext="{parentContext}"
toolbarClickCallback="{_handleToolbarButtonClick.bind(this)}"
button="{button}"
key="{button.id}"
activeButtons="{activeButtonsIds}"
isActive="{isActive}"
/>
```
| Property | Type | Description |
| ---------------------- | -------- | ------------------------------- |
| `activeButtons` | string[] | list of active buttons |
| `button` | object | its own definition object |
| `key` | string | React key prop |
| `isActive` | boolean | If current button is active |
| `parentContext` | ? | The parent component's context? |
| `toolbarClickCallback` | func | Callback method for clicks |
+32 -15
View File
@@ -1,19 +1,39 @@
# Module: Viewport
An extension can register a Viewport Module by providing a `getViewportModule()`
method that returns a React Component. The React component will receive the
following props:
An extension can register a Viewport Module by defining a `getViewportModule`
method that returns a React component. Currently, we use viewport components to
add support for:
```js
children: PropTypes.arrayOf(PropTypes.element)
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportData: PropTypes.object, // { studies, displaySet }
viewportIndex: PropTypes.number,
children: PropTypes.node,
customProps: PropTypes.object
- 2D Medical Image Viewing (cornerstone ext.)
- Structured Reports as HTML (dicom html ext.)
- Encapsulated PDFs as PDFs (dicom pdf ext.)
- Whole Slide Microscopy Viewing (whole slide ext.)
- etc.
The general pattern is, the [`sopClassHandlerModule`](#) helps us determine
which Viewport Component a set of `sopClassUIDs` should default to. The Viewport
Component receives props containing a display set it should know how to render.
## Viewport Component Props
Each `ViewportComponent` will receive the following props:
```html
<viewportComponent
viewportData="{viewportData}"
viewportIndex="{viewportIndex}"
children="{[children]}"
/>
```
| Property | Type | Description |
| --------------- | --------------- | --------------------------------- |
| `children` | React.element[] | |
| `viewportData` | object | `viewportSpecificData` (probably) |
| `viewportIndex` | number | |
### `@ohif/viewer`
Viewport components are managed by the `ViewportGrid` Component. Which Viewport
component is used depends on:
@@ -23,7 +43,4 @@ component is used depends on:
![Cornerstone Viewport](../../assets/img/extensions-viewport.png)
<center><i>An example of three Viewports</i></center>
For a complete example implementation,
[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js).
<center><i>An example of three cornerstone Viewports</i></center>
File renamed without changes.
@@ -19,10 +19,10 @@ Follow the steps outlined in the [Bug Report Template][bug-report-template].
At the moment we are in the process of defining our roadmap and will do our best
to communicate this to the community. If your requested feature is on the
roadmap, then it will most likely be built at some point. If it is not, you are
welcome to build it yourself and [contribute it](contributing/index.md). If you
have resources and would like to fund the development of a feature, please
[contact us](http://www.ohif.org) or work with community members that offer
[consulting services][commercial-support].
welcome to build it yourself and [contribute it](development/contributing.md).
If you have resources and would like to fund the development of a feature,
please [contact us](http://www.ohif.org) or work with community members that
offer [consulting services][commercial-support].
### Who should I contact about Academic Collaborations?
@@ -1,4 +1,4 @@
# Installation
# PWA vs Packaged
It's important to know that the OHIF Viewer project provides two different build
processes:
@@ -36,7 +36,7 @@ many data sources. The OHIF Viewer's scope **DOES** include configuration and
support for services that are protected with OpenID-Connect.
In an effort to aide our users and contributors, we attempt to provide several
[deployment and hosting recipes](./../deployment/index.md) as potential starting
[deployment and hosting recipes](./deployment/index.md) as potential starting
points. These are not meant to be rock solid, production ready, solutions; like
most recipes, they should be augmented to best fit you and your organization's
taste, preferences, etc.
+1 -1
View File
@@ -105,7 +105,7 @@ quality and test coverage must not changed by a significant margin. For some
repositories, visual screenshot-based tests are also included, and video
recordings of end-to-end tests are stored for later review.
[You can read more about our continous integration efforts here](/continous-integration.md)
[You can read more about our continous integration efforts here](/development/continous-integration.md)
## Releases
+28
View File
@@ -0,0 +1,28 @@
# Quick Start
This page details how to get an instance of the OHIF Viewer up and running as
fast as possible. It shows how to grab a pre-built version of the application,
point it at your data source (PACS), and plop it on a web server.
## Options
### 1. Pre-built PWA
...
### 2. Script-Tag
...
### 3. Docker
...
## Security Concerns
- Secure your data
## Common Issues
- Missing server rewrite rules
- CORS issues when requesting data from PACS
@@ -1,6 +1,6 @@
# Measurements Package (ohif-measurements)
# Service: Measurements
## Package design
...
## Usage
+1 -1
View File
@@ -43,7 +43,7 @@ translate to in practice?
```js
// In the application
const UINotificationService = createUINotificationService();
import UINotificationService from '@ohif/core';
const servicesManager = new ServicesManager();
servicesManager.registerService(UINotificationService);
+67
View File
@@ -0,0 +1,67 @@
# Viewer: Configuration
We maintain a number of common viewer application configurations at
[`<root>/platform/viewer/public/configs`][config-dir]. How these values are
passed to the viewer depend on how it's deployed, but the two most common paths
are:
- `index.html` looks for `https://your-website.com/app-config.js` OR
- `index.html` passes the values to `OHIF.installViewer()`
```js
window.config = {
routerBasename: '/',
whiteLabelling: {},
extensions: [],
showStudyList: true,
filterQueryParam: false,
servers: {
dicomWeb: [
{
name: 'DCM4CHEE',
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
},
],
},
// Supported Keys: https://craig.is/killing/mice
hotkeys: [
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
label: 'Flip Vertically',
keys: ['v'],
},
],
/* Configuration passed to the bundled cornerstone extension
*
* The cornerstone extension is currently tightly coupled to the platform.
* Until we're able to decouple it, this key will serve as a workaround to
* pass it configuration.
*/
cornerstoneExtensionConfig: {
/* Whether to show/hide annotation "handles" */
hideHandles: true,
},
};
```
<!--
LINKS
-->
<!-- prettier-ignore-start -->
[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config
<!-- prettier-ignore-end -->
@@ -0,0 +1,23 @@
# Environment Variables
There are a number of environment variables we use at build time to influence the output application's behavior.
```bash
# Application
NODE_ENV=< production | development >
DEBUG=< true | false >
APP_CONFIG=< relative path to application configuration file >
PUBLIC_URL=<>
VERSION_NUMBER=<Set by CircleCI>
BUILD_NUM=<Set by CircleCI>
# i18n
USE_LOCIZE=<false>
LOCIZE_PROJECTID=<ProjectID to pull translations for>
LOCIZE_API_KEY=<To enable Locize live editing of translations>
```
## Setting Environment Variables
- `npx cross-env`
- `.env` files
- env variables on build machine, or for terminal session
File renamed without changes.
+9
View File
@@ -0,0 +1,9 @@
# Viewer
The OHIF Viewing Platform strives to be highly configurable and extensible. This
makes it easier for our community members to keep their "secret sauce" private,
and incentivises contributions back to the platform. The `@ohif/viewer` project
of the platform is the lynchpin that combines everything to create our
application.
- When configuration and themeing aren't enough
@@ -1,4 +1,4 @@
# Translating
# Viewer: Internationalization
OHIF supports internationalization using [i18next](https://www.i18next.com/)
through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n),
@@ -15,7 +15,7 @@ where is the main instance of i18n containing several languages and tools.
</div>
</div>
### Installing
## Installing
```bash
yarn add @ohif/i18n
@@ -25,7 +25,7 @@ yarn add @ohif/i18n
npm install --save @ohif/i18n
```
### How it works
## How it works
After installing `@ohif/i18n` npm package, the translation function
[t](https://www.i18next.com/overview/api#t) can be used [with](#with-react) or
@@ -57,13 +57,13 @@ If the translation.json file contains a key that matches the HTML content e.g.
---
#### With React
### With React
This section will introduce you to [react-i18next](https://react.i18next.com/)
basics and show how to implement the [t](https://www.i18next.com/overview/api#t)
function easily.
##### Using HOCs
#### Using HOCs
In most cases we used
[High Order Components](https://react.i18next.com/latest/withtranslation-hoc) to
@@ -86,13 +86,13 @@ export default withTranslation('MyNameSpace')(MyComponent);
> [I18nextProvider](#using-outside-of-ohif-viewer) section, `withTranslation`
> HOC doesnt works without a I18nextProvider
##### Using Hooks
#### Using Hooks
Also, it's possible to get the `t` tool using
[React Hooks](https://react.i18next.com/latest/usetranslation-hook), but it
requires at least React > 16.8 😉
#### Using outside of OHIF viewer
### Using outside of OHIF viewer
OHIF Viewer already sets a main
[I18nextProvider](https://react.i18next.com/latest/i18nextprovider) connected to
@@ -119,7 +119,7 @@ usage.
---
#### Without React
### Without React
When needed, you can also use available translations _without React_.
@@ -1,4 +1,4 @@
# Themeing
# Viewer: Themeing
Themeing is currently accomplished with color variables that are defined within
the [`:root`](https://css-tricks.com/almanac/selectors/r/root/) selector
+4 -2
View File
@@ -12,8 +12,10 @@ export default {
*/
preRegistration({
servicesManager,
configuration: extensionConfiguration,
servicesManager = {},
commandsManager = {},
appConfig = {},
configuration = {},
}) {},
/**
+82
View File
@@ -3,6 +3,88 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.1...@ohif/extension-cornerstone@2.2.2) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20)
### Bug Fixes
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.1...@ohif/extension-cornerstone@2.2.0) (2019-12-20)
### Features
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.0...@ohif/extension-cornerstone@2.1.1) (2019-12-16)
### Bug Fixes
* 🐛 add WwwcRegionTool to cornerstone tools initialization ([#1302](https://github.com/OHIF/Viewers/issues/1302)) ([d5bf728](https://github.com/OHIF/Viewers/commit/d5bf72851a32dff9fd3fc09332ea5250bc7e6114))
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11)
### Features
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
## [2.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.1...@ohif/extension-cornerstone@2.0.2) (2019-12-11)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.0...@ohif/extension-cornerstone@2.0.1) (2019-12-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.2...@ohif/extension-cornerstone@2.0.0) (2019-12-09)
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
### BREAKING CHANGES
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.1...@ohif/extension-cornerstone@1.7.2) (2019-12-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
+32
View File
@@ -67,6 +67,38 @@ Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]
and is connected the redux store. This module is the most prone to change as we
hammer out our Viewport interface.
## Tool Configuration
Tools can be configured through extension configuration using the tools key:
```js
...
cornerstoneExtensionConfig: {
tools: {
ArrowAnnotate: {
configuration: {
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
},
},
},
},
...
```
## Annotate Tools Configuration
*We currently support one property for annotation tools.*
### Hide handles
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
```js
...
cornerstoneExtensionConfig: {
hideHandles: true,
},
...
## Resources
### Repositories
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "1.7.2",
"version": "2.2.2",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -32,9 +32,9 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.8.0",
"cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
"dcmjs": "^0.8.2",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
@@ -47,6 +47,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"classnames": "^2.2.6",
"lodash.merge": "^4.6.2",
"lodash.throttle": "^4.1.1",
"query-string": "^6.8.3",
"react-cornerstone-viewport": "2.x.x"
@@ -248,10 +248,13 @@ class OHIFCornerstoneViewport extends Component {
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
childrenWithProps = this.props.children.map((child, index) => {
return React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index,
});
return (
child &&
React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index,
})
);
});
}
+117 -7
View File
@@ -2,6 +2,7 @@ import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import OHIF from '@ohif/core';
import setCornerstoneLayout from './utils/setCornerstoneLayout.js';
import { getEnabledElement } from './state';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
const scroll = cornerstoneTools.import('util/scroll');
@@ -148,18 +149,121 @@ const commandsModule = ({ servicesManager }) => {
showDownloadViewportModal: ({ title, viewports }) => {
const activeViewportIndex = viewports.activeViewportIndex;
const { UIModalService } = servicesManager.services;
UIModalService.show({
content: CornerstoneViewportDownloadForm,
title,
contentProps: {
activeViewportIndex,
onClose: UIModalService.hide,
},
if (UIModalService) {
UIModalService.show({
content: CornerstoneViewportDownloadForm,
title,
contentProps: {
activeViewportIndex,
onClose: UIModalService.hide,
},
});
}
},
updateTableWithNewMeasurementData({
toolType,
measurementNumber,
location,
description,
}) {
// Update all measurements by measurement number
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
const measurements = measurementApi.tools[toolType].filter(
m => m.measurementNumber === measurementNumber
);
measurements.forEach(measurement => {
measurement.location = location;
measurement.description = description;
measurementApi.updateMeasurement(measurement.toolType, measurement);
});
measurementApi.syncMeasurementsAndToolData();
// Update images in all active viewports
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
},
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
const nearbyTool = {};
let pointNearTool = false;
availableToolTypes.forEach(toolType => {
const elementToolData = cornerstoneTools.getToolState(
element,
toolType
);
if (!elementToolData) {
return;
}
elementToolData.data.forEach((toolData, index) => {
let elementToolInstance = cornerstoneTools.getToolForElement(
element,
toolType
);
if (!elementToolInstance) {
elementToolInstance = cornerstoneTools.getToolForElement(
element,
`${toolType}Tool`
);
}
if (!elementToolInstance) {
console.warn('Tool not found.');
return undefined;
}
if (
elementToolInstance.pointNearTool(
element,
toolData,
canvasCoordinates
)
) {
pointNearTool = true;
nearbyTool.tool = toolData;
nearbyTool.index = index;
nearbyTool.toolType = toolType;
}
});
if (pointNearTool) {
return false;
}
});
return pointNearTool ? nearbyTool : undefined;
},
removeToolState: ({ element, toolType, tool }) => {
cornerstoneTools.removeToolState(element, toolType, tool);
cornerstone.updateImage(element);
},
setCornerstoneLayout: () => {
setCornerstoneLayout();
}
};
const definitions = {
getNearbyToolData: {
commandFn: actions.getNearbyToolData,
storeContexts: [],
options: {},
},
removeToolState: {
commandFn: actions.removeToolState,
storeContexts: [],
options: {},
},
updateTableWithNewMeasurementData: {
commandFn: actions.updateTableWithNewMeasurementData,
storeContexts: [],
options: {},
},
showDownloadViewportModal: {
commandFn: actions.showDownloadViewportModal,
storeContexts: ['viewports'],
@@ -248,6 +352,12 @@ const commandsModule = ({ servicesManager }) => {
storeContexts: [],
options: {},
},
setCornerstoneLayout: {
commandFn: actions.setCornerstoneLayout,
storeContexts: [],
options: {},
context: 'VIEWER',
},
};
return {
+120 -74
View File
@@ -1,9 +1,10 @@
import OHIF from '@ohif/core';
import { SimpleDialog } from '@ohif/ui';
import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools';
import initCornerstoneTools from './initCornerstoneTools.js';
import merge from 'lodash.merge';
import queryString from 'query-string';
import { SimpleDialog } from '@ohif/ui';
import initCornerstoneTools from './initCornerstoneTools.js';
function fallbackMetaDataProvider(type, imageId) {
if (!imageId.includes('wado?requestType=WADO')) {
@@ -26,30 +27,33 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
/**
*
* @param {object} configuration
* @param {Object} servicesManager
* @param {Object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
export default function init({ servicesManager, configuration = {} }) {
const { UIDialogService } = servicesManager.services;
export default function init({ servicesManager, configuration }) {
const callInputDialog = (data, event, callback) => {
let dialogId = UIDialogService.create({
content: SimpleDialog.InputDialog,
defaultPosition: {
x: (event && event.currentPoints.canvas.x) || 0,
y: (event && event.currentPoints.canvas.y) || 0,
},
showOverlay: true,
contentProps: {
title: 'Enter your annotation',
label: 'New label',
measurementData: data ? { description: data.text } : {},
onClose: () => UIDialogService.dismiss({ id: dialogId }),
onSubmit: value => {
callback(value);
UIDialogService.dismiss({ id: dialogId });
const { UIDialogService } = servicesManager.services;
if (UIDialogService) {
let dialogId = UIDialogService.create({
centralize: true,
isDraggable: false,
content: SimpleDialog.InputDialog,
useLastPosition: false,
showOverlay: true,
contentProps: {
title: 'Enter your annotation',
label: 'New label',
measurementData: data ? { description: data.text } : {},
onClose: () => UIDialogService.dismiss({ id: dialogId }),
onSubmit: value => {
callback(value);
UIDialogService.dismiss({ id: dialogId });
},
},
},
});
});
}
};
const { csToolsConfig } = configuration;
@@ -72,61 +76,102 @@ export default function init({ servicesManager, configuration = {} }) {
initCornerstoneTools(defaultCsToolsConfig);
// ~~ Toooools 🙌
const {
PanTool,
ZoomTool,
WwwcTool,
MagnifyTool,
StackScrollTool,
StackScrollMouseWheelTool,
// Touch
PanMultiTouchTool,
ZoomTouchPinchTool,
// Annotations
EraserTool,
BidirectionalTool,
LengthTool,
AngleTool,
FreehandRoiTool,
EllipticalRoiTool,
DragProbeTool,
RectangleRoiTool,
// Segmentation
BrushTool,
} = csTools;
const tools = [
PanTool,
ZoomTool,
WwwcTool,
MagnifyTool,
StackScrollTool,
StackScrollMouseWheelTool,
// Touch
PanMultiTouchTool,
ZoomTouchPinchTool,
// Annotations
EraserTool,
BidirectionalTool,
LengthTool,
AngleTool,
FreehandRoiTool,
EllipticalRoiTool,
DragProbeTool,
RectangleRoiTool,
// Segmentation
BrushTool,
];
const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
annotations: [
csTools.ArrowAnnotateTool,
csTools.EraserTool,
csTools.BidirectionalTool,
csTools.LengthTool,
csTools.AngleTool,
csTools.FreehandRoiTool,
csTools.EllipticalRoiTool,
csTools.DragProbeTool,
csTools.RectangleRoiTool,
],
segmentation: [csTools.BrushTool],
other: [
csTools.PanTool,
csTools.ZoomTool,
csTools.WwwcTool,
csTools.WwwcRegionTool,
csTools.MagnifyTool,
csTools.StackScrollTool,
csTools.StackScrollMouseWheelTool,
csTools.OverlayTool,
],
};
tools.forEach(tool => csTools.addTool(tool));
let tools = [];
Object.keys(toolsGroupedByType).forEach(toolsGroup =>
tools.push(...toolsGroupedByType[toolsGroup])
);
csTools.addTool(csTools.ArrowAnnotateTool, {
configuration: {
getTextCallback: (callback, eventDetails) =>
callInputDialog(null, eventDetails, callback),
changeTextCallback: (data, eventDetails, callback) =>
callInputDialog(data, eventDetails, callback),
/* Add extension tools configuration here. */
const internalToolsConfig = {
ArrowAnnotate: {
configuration: {
getTextCallback: (callback, eventDetails) =>
callInputDialog(null, eventDetails, callback),
changeTextCallback: (data, eventDetails, callback) =>
callInputDialog(data, eventDetails, callback),
},
},
};
/* Abstract tools configuration using extension configuration. */
const parseToolProps = (props, tool) => {
const { annotations } = toolsGroupedByType;
// An alternative approach would be to remove the `drawHandlesOnHover` config
// from the supported configuration properties in `cornerstone-tools`
const toolsWithHideableHandles = annotations.filter(
tool => !['RectangleRoiTool', 'EllipticalRoiTool'].includes(tool.name)
);
let parsedProps = { ...props };
/**
* drawHandles - Never/Always show handles
* drawHandlesOnHover - Only show handles on handle hover (pointNearHandle)
*
* Does not apply to tools where handles aren't placed in predictable
* locations.
*/
if (
configuration.hideHandles !== false &&
toolsWithHideableHandles.includes(tool)
) {
if (props.configuration) {
parsedProps.configuration.drawHandlesOnHover = true;
} else {
parsedProps.configuration = { drawHandlesOnHover: true };
}
}
return parsedProps;
};
/* Add tools with its custom props through extension configuration. */
tools.forEach(tool => {
const toolName = tool.name.replace('Tool', '');
const externalToolsConfig = configuration.tools || {};
const externalToolProps = externalToolsConfig[toolName] || {};
const internalToolProps = internalToolsConfig[toolName] || {};
const props = merge(
internalToolProps,
parseToolProps(externalToolProps, tool)
);
csTools.addTool(tool, props);
});
// TODO -> We need a better way to do this with maybe global tool state setting all tools passive.
const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');
tools.forEach(tool => {
if (tool.prototype instanceof BaseAnnotationTool) {
// BaseAnnotationTool would likely come from csTools lib exports
const toolName = new tool().name;
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
}
});
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
@@ -135,4 +180,5 @@ export default function init({ servicesManager, configuration = {} }) {
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
csTools.setToolActive('ZoomTouchPinch', {});
csTools.setToolEnabled('Overlay', {});
}
@@ -237,6 +237,15 @@ const definitions = [
},
],
},
{
id: 'Exit2DMPR',
label: 'Exit 2D MPR',
icon: 'times',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'setCornerstoneLayout',
context: 'ACTIVE_VIEWPORT::VTK',
}
];
export default {
@@ -0,0 +1,22 @@
import { redux } from '@ohif/core';
const { setLayout } = redux.actions;
/**
* Update the current layout with a simple Cornerstone one
*
* @return void
*/
const setCornerstoneLayout = () => {
const layout = {
numRows: 1,
numColumns: 1,
viewports: [{ plugin: 'cornerstone' }],
};
const action = setLayout(layout);
window.store.dispatch(action);
}
export default setCornerstoneLayout;
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.0.2...@ohif/extension-dicom-html@1.1.0) (2019-12-11)
### Features
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.0.1...@ohif/extension-dicom-html@1.0.2) (2019-12-02)
**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.0.2",
"version": "1.1.0",
"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.6.1",
"dcmjs": "^0.8.2",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.0...@ohif/extension-dicom-pdf@1.0.1) (2019-12-16)
### Bug Fixes
* 🐛 Update ohif pdf extension to optionally use pdfjs ([#1162](https://github.com/OHIF/Viewers/issues/1162)) ([31e542d](https://github.com/OHIF/Viewers/commit/31e542dfd973c3e982ede6d02d79bfe65175e792)), closes [#1049](https://github.com/OHIF/Viewers/issues/1049)
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@0.50.4...@ohif/extension-dicom-pdf@1.0.0) (2019-09-27)
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "1.0.0",
"version": "1.0.1",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@@ -37,7 +37,8 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"classnames": "^2.2.6",
"lodash.isequal": "^4.5.0"
"lodash.isequal": "^4.5.0",
"pdfjs-dist": "^2.2.228"
},
"gitHead": "a5baa9228c0eda0df880136bde4420d78e6f8706"
}
@@ -0,0 +1,27 @@
import OHIF from '@ohif/core';
import { connect } from 'react-redux';
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport';
const { setViewportActive } = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
const { activeViewportIndex } = state.viewports;
return { activeViewportIndex };
};
const mapDispatchToProps = (dispatch, ownProps) => {
const { viewportIndex } = ownProps;
return {
setViewportActive: () => {
dispatch(setViewportActive(viewportIndex));
},
};
};
const ConnectedOHIFDicomPDFViewer = connect(
mapStateToProps,
mapDispatchToProps
)(OHIFDicomPDFViewport);
export default ConnectedOHIFDicomPDFViewer;
@@ -0,0 +1,62 @@
.DicomPDFViewport {
--header-height: 50px;
}
.DicomPDFViewport #toolbar {
display: flex;
align-items: center;
color: #fff;
padding: 0.5em;
border-bottom: var(--ui-border-thickness) solid var(--ui-border-color);
height: var(--header-height);
}
.DicomPDFViewport #canvas {
height: calc(100% - var(--header-height));
overflow-y: scroll;
}
.DicomPDFViewport #text-layer {
height: 100%;
margin-top: var(--header-height);
}
.DicomPDFViewport #pdf-canvas-container {
position: relative;
}
.DicomPDFViewport canvas, #text-layer {
display: block;
margin: 0 auto;
overflow: hidden;
}
.DicomPDFViewport #toolbar button {
color: currentColor;
background-color: transparent;
font: inherit;
border: var(--ui-border-thickness) solid var(--ui-border-color);
border-radius: 3px;
padding: 0.25em 0.5em;
margin-right: 0.5em;
cursor: pointer;
}
.DicomPDFViewport #text-layer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1;
}
.DicomPDFViewport #text-layer>span {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
+197 -23
View File
@@ -1,6 +1,14 @@
import React, { Component } from 'react';
import React, { Component, createRef } from 'react';
import dicomParser from 'dicom-parser';
import PDFJS from 'pdfjs-dist';
import PropTypes from 'prop-types';
import TypedArrayProp from './TypedArrayProp';
import './DicomPDFViewport.css';
import pdfjsBuild from 'pdfjs-dist/build/pdf';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
pdfjsBuild.GlobalWorkerOptions.workerSrc = pdfjsWorker;
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
@@ -8,16 +16,88 @@ const SOP_CLASS_UIDS = {
};
class DicomPDFViewport extends Component {
state = {
fileURL: null,
error: null,
};
constructor(props) {
super(props);
this.state = {
fileURL: null,
error: null,
currentPageIndex: 1,
pdf: null,
scale: 1,
};
this.canvas = createRef();
this.textLayer = createRef();
}
static propTypes = {
byteArray: TypedArrayProp.uint8,
useNative: PropTypes.bool,
viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number,
setViewportActive: PropTypes.func,
viewportIndex: PropTypes.number,
};
renderPDF = (dataSet, byteArray) => {
static defaultProps = {
useNative: false,
};
async componentDidMount() {
const dataSet = this.parseByteArray(this.props.byteArray);
const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray);
this.setState(state => ({ ...state, fileURL }));
if (!this.props.useNative) {
const pdf = await PDFJS.getDocument(fileURL).promise;
this.setState(state => ({ ...state, pdf }), () => this.updatePDFCanvas());
}
}
updatePDFCanvas = async () => {
const { pdf, scale, currentPageIndex } = this.state;
const context = this.canvas.getContext('2d');
const page = await pdf.getPage(currentPageIndex);
let viewport = page.getViewport({ scale });
this.canvas.height = viewport.height;
this.canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport,
};
await page.render(renderContext);
const textContent = await page.getTextContent();
this.textLayer.innerHTML = '';
this.textLayer.style.height = viewport.height + 'px';
this.textLayer.style.width = viewport.width + 'px';
PDFJS.renderTextLayer({
textContent,
container: this.textLayer,
viewport,
textDivs: [],
});
};
componentDidUpdate(prevProps, prevState) {
const { currentPageIndex, scale } = this.state;
const newValidScale = prevState.scale !== scale && scale > 0;
const newValidPageNumber =
prevState.currentPageIndex !== currentPageIndex && currentPageIndex > 0;
if (newValidScale || newValidPageNumber) {
this.updatePDFCanvas();
}
}
getPDFFileUrl = (dataSet, byteArray) => {
let pdfByteArray = byteArray;
if (dataSet) {
@@ -36,50 +116,144 @@ class DicomPDFViewport extends Component {
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
const fileURL = URL.createObjectURL(PDF);
this.setState({
fileURL,
});
return fileURL;
};
onPageChange = async event => {
const { currentPageIndex, pdf } = this.state;
let newPageIndex = currentPageIndex;
const action = event.target.getAttribute('data-pager');
if (action === 'prev') {
if (currentPageIndex === 1) {
return;
}
newPageIndex -= 1;
if (currentPageIndex < 0) {
newPageIndex = 0;
}
}
if (action === 'next') {
if (currentPageIndex === pdf.numPages - 1) {
return;
}
newPageIndex += 1;
if (currentPageIndex > pdf.numPages - 1) {
newPageIndex = pdf.numPages - 1;
}
}
this.setState(state => ({ ...state, currentPageIndex: newPageIndex }));
};
onZoomChange = () => {
let newZoomValue = this.state.scale;
const action = event.target.getAttribute('data-pager');
if (action === '+') {
newZoomValue += 0.25;
}
if (action === '-') {
newZoomValue -= 0.25;
}
this.setState(state => ({ ...state, scale: newZoomValue }));
};
parseByteArray = byteArray => {
const options = {
untilTag: '',
};
const options = { untilTag: '' };
let dataSet;
try {
dataSet = dicomParser.parseDicom(byteArray, options);
} catch (error) {
this.setState({
error,
});
this.setState(state => ({ ...state, error }));
}
return dataSet;
};
componentDidMount() {
const dataSet = this.parseByteArray(this.props.byteArray);
setViewportActiveHandler = () => {
const {
setViewportActive,
viewportIndex,
activeViewportIndex,
} = this.props;
this.renderPDF(dataSet, this.props.byteArray);
}
if (viewportIndex !== activeViewportIndex) {
setViewportActive(viewportIndex);
}
};
downloadPDFCanvas = () => {
const { fileURL } = this.state;
const a = document.createElement('a');
a.href = fileURL;
a.download = fileURL.substr(fileURL.lastIndexOf('/') + 1);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
render() {
const { fileURL, pdf, error } = this.state;
return (
<div
className={'DicomPDFViewport'}
onClick={this.setViewportActiveHandler}
onScroll={this.setViewportActiveHandler}
style={{ width: '100%', height: '100%' }}
>
{this.state.fileURL && (
{!this.props.useNative ? (
<>
<div id="toolbar">
<div id="pager">
{pdf && pdf.numPages > 1 && (
<>
<button data-pager="prev" onClick={this.onPageChange}>
{`<`}
</button>
<button data-pager="next" onClick={this.onPageChange}>
{`>`}
</button>
</>
)}
<button data-pager="-" onClick={this.onZoomChange}>
{`-`}
</button>
<button data-pager="+" onClick={this.onZoomChange}>
{`+`}
</button>
<button onClick={this.downloadPDFCanvas}>Download</button>
</div>
</div>
<div id="canvas">
<div id="pdf-canvas-container">
<canvas
id="pdf-canvas"
ref={canvas => (this.canvas = canvas)}
/>
<div
id="text-layer"
ref={textLayer => (this.textLayer = textLayer)}
></div>
</div>
</div>
</>
) : (
<object
data={this.state.fileURL}
aria-label="PDF Viewer"
data={fileURL}
type="application/pdf"
width="100%"
height="100%"
/>
)}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
{error && <h2>{JSON.stringify(error)}</h2>}
</div>
);
}
@@ -11,6 +11,9 @@ class OHIFDicomPDFViewport extends Component {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number,
setViewportActive: PropTypes.func,
};
state = {
@@ -30,42 +33,36 @@ class OHIFDicomPDFViewport extends Component {
componentDidMount() {
const { displaySet, studies } = this.props.viewportData;
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders,
} = displaySet;
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
data => {
const byteArray = new Uint8Array(data);
this.setState({
byteArray: byteArray,
});
},
data => this.setState({ byteArray: new Uint8Array(data) }),
error => {
this.setState({
error,
});
this.setState({ error });
throw new Error(error);
}
);
}
render() {
const {
setViewportActive,
viewportIndex,
activeViewportIndex,
} = this.props;
const { byteArray, error } = this.state;
const { id, init, destroy } = OHIFDicomPDFViewport;
const pluginProps = { id, init, destroy };
return (
<OHIFComponentPlugin {...pluginProps}>
{this.state.byteArray && (
<DicomPDFViewport byteArray={this.state.byteArray} />
{byteArray && (
<DicomPDFViewport
byteArray={byteArray}
setViewportActive={setViewportActive}
viewportIndex={viewportIndex}
activeViewportIndex={activeViewportIndex}
/>
)}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
{error && <h2>{JSON.stringify(error)}</h2>}
</OHIFComponentPlugin>
);
}
@@ -0,0 +1,37 @@
/**
* 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;
}
+9 -4
View File
@@ -1,16 +1,21 @@
import asyncComponent from './asyncComponent.js';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
import OHIFDicomPDFViewport from './OHIFDicomPDFViewport.js';
const ConnectedOHIFDicomPDFViewer = asyncComponent(() =>
import(
/* webpackChunkName: "ConnectedOHIFDicomPDFViewer" */ './ConnectedOHIFDicomPDFViewer'
)
);
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'pdf',
getViewportModule() {
return OHIFDicomPDFViewport;
return ConnectedOHIFDicomPDFViewer;
},
getSopClassHandlerModule() {
return OHIFDicomPDFSopClassHandler;
}
},
};
+250
View File
@@ -3,6 +3,256 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.13...@ohif/extension-vtk@1.2.14) (2020-02-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.12...@ohif/extension-vtk@1.2.13) (2020-01-30)
### Bug Fixes
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
## [1.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.11...@ohif/extension-vtk@1.2.12) (2020-01-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.10...@ohif/extension-vtk@1.2.11) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.9...@ohif/extension-vtk@1.2.10) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.8...@ohif/extension-vtk@1.2.9) (2020-01-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.7...@ohif/extension-vtk@1.2.8) (2020-01-24)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.5...@ohif/extension-vtk@1.2.6) (2020-01-07)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.4...@ohif/extension-vtk@1.2.5) (2020-01-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.3...@ohif/extension-vtk@1.2.4) (2019-12-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.2...@ohif/extension-vtk@1.2.3) (2019-12-20)
### Bug Fixes
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.1...@ohif/extension-vtk@1.2.2) (2019-12-20)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.0...@ohif/extension-vtk@1.2.1) (2019-12-20)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.7...@ohif/extension-vtk@1.2.0) (2019-12-20)
### Features
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
## [1.1.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.6...@ohif/extension-vtk@1.1.7) (2019-12-19)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.5...@ohif/extension-vtk@1.1.6) (2019-12-18)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.4...@ohif/extension-vtk@1.1.5) (2019-12-16)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.2...@ohif/extension-vtk@1.1.3) (2019-12-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.1...@ohif/extension-vtk@1.1.2) (2019-12-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.0...@ohif/extension-vtk@1.1.1) (2019-12-11)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.2...@ohif/extension-vtk@1.1.0) (2019-12-11)
### Features
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.1...@ohif/extension-vtk@1.0.2) (2019-12-11)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.0...@ohif/extension-vtk@1.0.1) (2019-12-09)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.6...@ohif/extension-vtk@1.0.0) (2019-12-09)
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
### BREAKING CHANGES
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
## [0.54.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.5...@ohif/extension-vtk@0.54.6) (2019-12-07)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.54.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.4...@ohif/extension-vtk@0.54.5) (2019-12-07)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.54.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.3...@ohif/extension-vtk@0.54.4) (2019-12-07)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.54.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.2...@ohif/extension-vtk@0.54.3) (2019-12-06)
**Note:** Version bump only for package @ohif/extension-vtk
+6 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "0.54.3",
"version": "1.2.14",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -34,7 +34,7 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
"dcmjs": "^0.8.2",
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
@@ -49,14 +49,13 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"lodash.throttle": "^4.1.1",
"react-vtkjs-viewport": "^0.3.9"
"react-vtkjs-viewport": "^0.8.3"
},
"devDependencies": {
"@ohif/core": "^1.13.3",
"@ohif/ui": "^0.65.1",
"cornerstone-tools": "^4.8.0",
"@ohif/core": "^2.3.8",
"@ohif/ui": "^1.1.9",
"cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
"dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1",
"i18next": "^17.0.3",
+3 -2
View File
@@ -1,6 +1,6 @@
import OHIF from '@ohif/core';
import { View2D } from 'react-vtkjs-viewport';
import { connect } from 'react-redux';
import VTKViewport from './VTKViewport';
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
@@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => {
const pluginDetails = viewportLayout.vtk || {};
return {
activeViewportIndex: state.viewports.activeViewportIndex,
layout: state.viewports.layout,
isActive,
...pluginDetails,
@@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(View2D);
)(VTKViewport);
export default ConnectedVTKViewport;
+13 -4
View File
@@ -77,6 +77,11 @@ class OHIFVTKViewport extends Component {
}),
viewportIndex: PropTypes.number,
children: PropTypes.node,
onScroll: PropTypes.func,
};
static defaultProps = {
onScroll: () => {},
};
static id = 'OHIFVTKViewport';
@@ -358,10 +363,13 @@ class OHIFVTKViewport extends Component {
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
childrenWithProps = this.props.children.map((child, index) => {
return React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index,
});
return (
child &&
React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index,
})
);
});
}
@@ -383,6 +391,7 @@ class OHIFVTKViewport extends Component {
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
onScroll={this.props.onScroll}
/>
)}
</div>
+3
View File
@@ -0,0 +1,3 @@
.vtk-viewport-handler svg {
pointer-events: none;
}
+58
View File
@@ -0,0 +1,58 @@
import React, { useEffect, useCallback } from 'react';
import { View2D } from 'react-vtkjs-viewport';
import PropTypes from 'prop-types';
import './VTKViewport.css';
const VTKViewport = props => {
const style = { width: '100%', height: '100%', position: 'relative' };
const setViewportActiveHandler = useCallback(() => {
const { setViewportActive, viewportIndex, activeViewportIndex } = props;
if (viewportIndex !== activeViewportIndex) {
// set in Connected
setViewportActive();
}
});
useEffect(() => {
const handleScrollEvent = evt => {
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
const viewportUid = vtkViewportApiReference.uid;
const viewportWasScrolled = viewportUid === evt.detail.uid;
if (viewportWasScrolled) {
setViewportActiveHandler();
}
};
window.addEventListener('vtkscrollevent', handleScrollEvent);
return () =>
window.removeEventListener('vtkscrollevent', handleScrollEvent);
}, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]);
return (
<div
className="vtk-viewport-handler"
style={style}
onClick={setViewportActiveHandler}
>
<View2D {...props} />
</div>
);
};
VTKViewport.propTypes = {
setViewportActive: PropTypes.func.isRequired,
viewportIndex: PropTypes.number.isRequired,
activeViewportIndex: PropTypes.number.isRequired,
/* Receives viewportIndex */
onScroll: PropTypes.func,
};
VTKViewport.defaultProps = {
onScroll: () => {},
};
export default VTKViewport;
+10 -12
View File
@@ -103,6 +103,9 @@ const commandsModule = ({ commandsManager }) => {
}
const actions = {
getVtkApis: ({ index }) => {
return apis[index];
},
axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
@@ -234,7 +237,7 @@ const commandsModule = ({ commandsManager }) => {
},
},
{
// Sagital
// Sagittal
orientation: {
sliceNormal: [1, 0, 0],
viewUp: [0, 0, 1],
@@ -266,11 +269,12 @@ const commandsModule = ({ commandsManager }) => {
'crosshairsWidget'
);
const uid = api.uid;
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
api.setInteractorStyle({
istyle,
configuration: { apis, apiIndex },
configuration: { apis, apiIndex, uid },
});
});
},
@@ -296,55 +300,45 @@ const commandsModule = ({ commandsManager }) => {
},
enableRotateTool: {
commandFn: actions.enableRotateTool,
storeContexts: [],
options: {},
},
enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool,
storeContexts: [],
options: {},
},
enableLevelTool: {
commandFn: actions.enableLevelTool,
storeContexts: [],
options: {},
},
setBlendModeToComposite: {
commandFn: actions.setBlendModeToComposite,
storeContexts: [],
options: { blendMode: BlendMode.COMPOSITE_BLEND },
},
setBlendModeToMaximumIntensity: {
commandFn: actions.setBlendModeToMaximumIntensity,
storeContexts: [],
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
},
setBlendModeToMinimumIntensity: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
},
setBlendModeToAverageIntensity: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
},
setSlabThickness: {
// TODO: How do we pass in a function argument?
commandFn: actions.setSlabThickness,
storeContexts: [],
options: {},
},
increaseSlabThickness: {
commandFn: actions.changeSlabThickness,
storeContexts: [],
options: {
change: 3,
},
},
decreaseSlabThickness: {
commandFn: actions.changeSlabThickness,
storeContexts: [],
options: {
change: -3,
},
@@ -355,6 +349,10 @@ const commandsModule = ({ commandsManager }) => {
options: {},
context: 'VIEWER',
},
getVtkApiForViewportIndex: {
commandFn: actions.getVtkApis,
context: 'VIEWER',
},
};
return {
+3 -2
View File
@@ -1,6 +1,7 @@
import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import withCommandsManager from './withCommandsManager.js';
// This feels weird
// import loadLocales from './loadLocales';
@@ -14,8 +15,8 @@ const vtkExtension = {
*/
id: 'vtk',
getViewportModule() {
return OHIFVTKViewport;
getViewportModule({ commandsManager }) {
return withCommandsManager(OHIFVTKViewport, commandsManager);
},
getToolbarModule() {
return toolbarModule;
@@ -0,0 +1,87 @@
import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { ToolbarButton } from '@ohif/ui';
import { utils } from '@ohif/core';
const { studyMetadataManager } = utils;
let isVisible = true;
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
if (!viewportSpecificData[activeViewportIndex]) {
return false;
};
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
activeViewportIndex
];
const studies = studyMetadataManager.all();
const study = studies.find(
study => study.studyInstanceUID === studyInstanceUid
);
if (!study) {
return false;
}
const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid);
if (!displaySet) {
return false;
};
return displaySet.isReconstructable;
};
function VTKMPRToolbarButton({
parentContext,
toolbarClickCallback,
button,
activeButtons,
isActive,
className,
}) {
const { id, label, icon } = button;
const { viewportSpecificData, activeViewportIndex } = useSelector(state => {
const { viewports = {} } = state;
const { viewportSpecificData, activeViewportIndex } = viewports;
return {
viewportSpecificData,
activeViewportIndex,
}
});
isVisible = _isDisplaySetReconstructable(
viewportSpecificData,
activeViewportIndex,
);
return (
<React.Fragment>
{isVisible && (
<ToolbarButton
key={id}
label={label}
icon={icon}
onClick={evt => toolbarClickCallback(button, evt)}
isActive={isActive}
/>
)}
</React.Fragment>
);
}
VTKMPRToolbarButton.propTypes = {
parentContext: PropTypes.object.isRequired,
toolbarClickCallback: PropTypes.func.isRequired,
button: PropTypes.object.isRequired,
activeButtons: PropTypes.array.isRequired,
isActive: PropTypes.bool,
className: PropTypes.string,
};
export default VTKMPRToolbarButton;
+12 -1
View File
@@ -1,4 +1,5 @@
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
import VTKMPRToolbarButton from './toolbarComponents/VTKMPRToolbarButton';
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
@@ -115,9 +116,19 @@ const definitions = [
},
],
},
{
id: '2DMPR',
label: '2D MPR',
icon: 'cube',
//
CustomComponent: VTKMPRToolbarButton,
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'mpr2d',
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react';
export default function withCommandsManager(Component, commandsManager = {}) {
return class WithCommandsManager extends React.Component {
render() {
return (
<Component
{...this.props}
onScroll={viewportIndex =>
commandsManager.runCommand('getVtkApiForViewportIndex', {
index: viewportIndex,
})
}
/>
);
}
};
}
+1 -1
View File
@@ -59,6 +59,7 @@
"copy-webpack-plugin": "^5.0.3",
"cross-env": "^5.2.0",
"css-loader": "^3.2.0",
"cypress-file-upload": "^3.5.3",
"dotenv": "^8.1.0",
"eslint": "5.16.0",
"eslint-config-prettier": "^6.4.0",
@@ -72,7 +73,6 @@
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "1.5.0",
"extract-css-chunks-webpack-plugin": "^4.5.4",
"favicons-webpack-plugin": "^1.0.2",
"html-webpack-plugin": "^3.2.0",
"husky": "^3.0.0",
"jest": "^24.8.0",
+152
View File
@@ -3,6 +3,158 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.3.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.7...@ohif/core@2.3.8) (2020-02-06)
### Bug Fixes
* Remove trash data from redux storage after updates ([#1358](https://github.com/OHIF/Viewers/issues/1358)) ([7b2d44f](https://github.com/OHIF/Viewers/commit/7b2d44f2c18241ea521b8d3652aee32e36eaddb8))
## [2.3.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.6...@ohif/core@2.3.7) (2020-01-30)
### Bug Fixes
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
## [2.3.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.5...@ohif/core@2.3.6) (2020-01-28)
**Note:** Version bump only for package @ohif/core
## [2.3.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.4...@ohif/core@2.3.5) (2020-01-28)
**Note:** Version bump only for package @ohif/core
## [2.3.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.3...@ohif/core@2.3.4) (2020-01-27)
**Note:** Version bump only for package @ohif/core
## [2.3.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.2...@ohif/core@2.3.3) (2020-01-24)
**Note:** Version bump only for package @ohif/core
## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06)
**Note:** Version bump only for package @ohif/core
## [2.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.0...@ohif/core@2.3.1) (2019-12-30)
### Bug Fixes
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.2.1...@ohif/core@2.3.0) (2019-12-20)
### Features
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.2.0...@ohif/core@2.2.1) (2019-12-18)
**Note:** Version bump only for package @ohif/core
# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.1...@ohif/core@2.2.0) (2019-12-16)
### Features
* 🎸 Expose extension config to modules ([#1279](https://github.com/OHIF/Viewers/issues/1279)) ([4ea239a](https://github.com/OHIF/Viewers/commit/4ea239a9535ef297e23387c186e537ab273744ea)), closes [#1268](https://github.com/OHIF/Viewers/issues/1268)
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
**Note:** Version bump only for package @ohif/core
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11)
### Features
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
## [2.0.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.1...@ohif/core@2.0.2) (2019-12-11)
**Note:** Version bump only for package @ohif/core
## [2.0.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.0...@ohif/core@2.0.1) (2019-12-09)
**Note:** Version bump only for package @ohif/core
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.3...@ohif/core@2.0.0) (2019-12-09)
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
### BREAKING CHANGES
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
## [1.13.3](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.2...@ohif/core@1.13.3) (2019-12-06)
**Note:** Version bump only for package @ohif/core
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "1.13.3",
"version": "2.3.8",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -32,13 +32,14 @@
},
"peerDependencies": {
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.8.0",
"cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "^0.8.3",
"dicomweb-client": "^0.5.2",
"isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0",
+80
View File
@@ -0,0 +1,80 @@
import log from '../log';
import studies from '../studies';
import utils from '../utils';
import {
retrieveMeasurementFromSR,
stowSRFromMeasurements,
} from './handleStructuredReport';
import findMostRecentStructuredReport from './utils/findMostRecentStructuredReport';
/**
*
* @typedef serverType
* @property {string} type - type of the server
* @property {string} wadoRoot - server wado root url
*
*/
/**
* Function to be registered into MeasurementAPI to retrieve measurements from DICOM Structured Reports
*
* @param {serverType} server
* @returns {Promise} Should resolve with OHIF measurementData object
*/
const retrieveMeasurements = server => {
log.info('[DICOMSR] retrieveMeasurements');
if (!server || server.type !== 'dicomWeb') {
log.error('[DICOMSR] DicomWeb server is required!');
return Promise.reject({});
}
const serverUrl = server.wadoRoot;
const studies = utils.studyMetadataManager.all();
const latestSeries = findMostRecentStructuredReport(studies);
if (!latestSeries) return Promise.resolve({});
return retrieveMeasurementFromSR(latestSeries, studies, serverUrl);
};
/**
* Function to be registered into MeasurementAPI to store measurements into DICOM Structured Reports
*
* @param {Object} measurementData - OHIF measurementData object
* @param {Object} filter
* @param {serverType} server
* @returns {Object} With message to be displayed on success
*/
const storeMeasurements = async (measurementData, filter, server) => {
log.info('[DICOMSR] storeMeasurements');
if (!server || server.type !== 'dicomWeb') {
log.error('[DICOMSR] DicomWeb server is required!');
return Promise.reject({});
}
const serverUrl = server.wadoRoot;
const firstMeasurementKey = Object.keys(measurementData)[0];
const firstMeasurement = measurementData[firstMeasurementKey][0];
const studyInstanceUid =
firstMeasurement && firstMeasurement.studyInstanceUid;
try {
await stowSRFromMeasurements(measurementData, serverUrl);
if (studyInstanceUid) {
studies.deleteStudyMetadataPromise(studyInstanceUid);
}
return {
message: 'Measurements were saved with success',
};
} catch (error) {
log.error(
`[DICOMSR] Error while saving the measurements: ${error.message}`
);
throw new Error('Error while saving the measurements.');
}
};
export { retrieveMeasurements, storeMeasurements };
@@ -0,0 +1,84 @@
import * as dcmjs from 'dcmjs';
import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb';
import parseDicomStructuredReport from './parseDicomStructuredReport';
import parseMeasurementsData from './parseMeasurementsData';
import getAllDisplaySets from './utils/getAllDisplaySets';
const VERSION_NAME = 'dcmjs-0.0';
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
/**
* Function to retrieve measurements from DICOM Structured Reports coming from determined server
*
* @param {Array} series - List of all series metaData loaded
* @param {Array} studies - List of all studies metaData loaded
* @param {string} serverUrl - Server URL to be used on request
* @returns {Object} MeasurementData
*/
const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
const config = {
url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(),
};
const dicomWeb = new api.DICOMwebClient(config);
const instance = series.getFirstInstance();
const options = {
studyInstanceUID: instance.getStudyInstanceUID(),
seriesInstanceUID: instance.getSeriesInstanceUID(),
sopInstanceUID: instance.getSOPInstanceUID(),
};
const part10SRArrayBuffer = await dicomWeb.retrieveInstance(options);
const displaySets = getAllDisplaySets(studies);
const measurementsData = parseDicomStructuredReport(
part10SRArrayBuffer,
displaySets
);
return measurementsData;
};
/**
* Function to store measurements to DICOM Structured Reports in determined server
*
* @param {Object} measurements - OHIF measurementData object
* @param {string} serverUrl - Server URL to be used on request
* @returns {Promise}
*/
const stowSRFromMeasurements = async (measurements, serverUrl) => {
const { dataset } = parseMeasurementsData(measurements);
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
const meta = {
FileMetaInformationVersion: dataset._meta.FileMetaInformationVersion.Value,
MediaStorageSOPClassUID: dataset.SOPClassUID,
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
TransferSyntaxUID: TRANSFER_SYNTAX_UID,
ImplementationClassUID: DicomMetaDictionary.uid(),
ImplementationVersionName: VERSION_NAME,
};
const denaturalized = DicomMetaDictionary.denaturalizeDataset(meta);
const dicomDict = new DicomDict(denaturalized);
dicomDict.dict = DicomMetaDictionary.denaturalizeDataset(dataset);
const part10Buffer = dicomDict.write();
const config = {
url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
datasets: [part10Buffer],
};
await dicomWeb.storeInstances(options);
};
export { retrieveMeasurementFromSR, stowSRFromMeasurements };
+10
View File
@@ -0,0 +1,10 @@
import { retrieveMeasurements, storeMeasurements } from './dataExchange';
import isToolSupported from './utils/isToolSupported';
const DICOMSR = {
retrieveMeasurements,
storeMeasurements,
isToolSupported,
};
export default DICOMSR;
@@ -0,0 +1,95 @@
import * as dcmjs from 'dcmjs';
import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid';
/**
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
* measurementData format is a viewer specific format to be stored into the redux and consumed by other components
* (e.g. measurement table)
*
* @param {ArrayBuffer} part10SRArrayBuffer
* @param {Array} displaySets
* @returns
*/
const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
// Get the dicom data as an Object
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
const { MeasurementReport } = dcmjs.adapters.Cornerstone;
const storedMeasurementByToolType = MeasurementReport.generateToolState(
dataset
);
const measurementData = {};
let measurementNumber = 0;
Object.keys(storedMeasurementByToolType).forEach(toolName => {
const measurements = storedMeasurementByToolType[toolName];
measurementData[toolName] = [];
measurements.forEach(measurement => {
const instanceMetadata = findInstanceMetadataBySopInstanceUid(
displaySets,
measurement.sopInstanceUid
);
const { _study: study, _series: series } = instanceMetadata;
const { studyInstanceUid, patientId } = study;
const { seriesInstanceUid } = series;
const { sopInstanceUid, frameIndex } = measurement;
const imagePath = getImagePath(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
frameIndex
);
const imageId = instanceMetadata.getImageId();
if (!imageId) {
return;
}
// TODO: We need the currentTimepointID set into the viewer
const currentTimepointId = 'TimepointId';
const toolData = Object.assign({}, measurement, {
imageId,
imagePath,
seriesInstanceUid,
studyInstanceUid,
patientId,
measurementNumber: ++measurementNumber,
timepointId: currentTimepointId,
toolType: toolName,
_id: imageId + measurementNumber,
});
measurementData[toolName].push(toolData);
});
});
return measurementData;
};
/**
* Function to create imagePath with all imageData related
*
* @param {string} studyInstanceUid
* @param {string} seriesInstanceUid
* @param {string} sopInstanceUid
* @param {string} frameIndex
* @returns
*/
const getImagePath = (
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
frameIndex
) => {
return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join(
'_'
);
};
export default parseDicomStructuredReport;
@@ -0,0 +1,57 @@
import * as dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core';
import log from '../log';
import measurements from '../measurements';
import isToolSupported from './utils/isToolSupported';
/**
* Function to parse OHIF viewer measurementData into a dcmjs MeasurementReport
*
* @param {Object} measurementsData - OHIF measurementData object
* @returns {Object} Dataset: measurement report from dcmjs
*/
const parseMeasurementsData = measurementsData => {
const { MeasurementReport } = dcmjs.adapters.Cornerstone;
const { getImageIdForImagePath } = measurements;
const toolState = {};
const unsupportedTools = [];
Object.keys(measurementsData).forEach(measurementType => {
const annotations = measurementsData[measurementType];
annotations.forEach(annotation => {
const { toolType, imagePath } = annotation;
if (isToolSupported(toolType)) {
const imageId = getImageIdForImagePath(imagePath);
toolState[imageId] = toolState[imageId] || {};
toolState[imageId][toolType] = toolState[imageId][toolType] || {
data: [],
};
toolState[imageId][toolType].data.push(annotation);
} else {
unsupportedTools.push(toolType);
}
});
});
if (unsupportedTools.length > 0) {
log.warn(
`[DICOMSR] Tooltypes not supported: ${unsupportedTools.join(', ')}`
);
}
const report = MeasurementReport.generateReport(
toolState,
cornerstone.metaData
);
return {
dataset: report.dataset,
};
};
export default parseMeasurementsData;
@@ -0,0 +1,24 @@
/**
* Should Find the requested instance metadata into the displaySets and return
*
* @param {Array} displaySets - List of displaySets
* @param {string} sopInstanceUid - sopInstanceUID to look for
* @returns {Object} - instance metadata found
*/
const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
let instanceFound;
displaySets.find(displaySet => {
if (!displaySet.images) return false;
instanceFound = displaySet.images.find(
instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid
);
return !!instanceFound;
});
return instanceFound;
};
export default findInstanceMetadataBySopInstanceUid;
@@ -0,0 +1,60 @@
/**
* Should find the most recent Structured Report metadata
*
* @param {Array} studies
* @returns {Object} Series
*/
const findMostRecentStructuredReport = studies => {
let mostRecentStructuredReport;
studies.forEach(study => {
const allSeries = study.getSeries ? study.getSeries() : [];
allSeries.forEach(series => {
if (isStructuredReportSeries(series)) {
if (
!mostRecentStructuredReport ||
compareSeriesDate(series, mostRecentStructuredReport)
) {
mostRecentStructuredReport = series;
}
}
});
});
return mostRecentStructuredReport;
};
/**
* Checks if series sopClassUID matches with the supported Structured Reports sopClassUID
*
* @param {Object} series - Series metadata
* @returns {boolean}
*/
const isStructuredReportSeries = series => {
const supportedSopClassUIDs = [
'1.2.840.10008.5.1.4.1.1.88.22',
'1.2.840.10008.5.1.4.1.1.11.1',
];
const firstInstance = series.getFirstInstance();
const sopClassUid = firstInstance._instance.sopClassUid;
return supportedSopClassUIDs.includes(sopClassUid);
};
/**
* Checkes if series1 is newer than series2
*
* @param {Object} series1 - Series Metadata 1
* @param {Object} series2 - Series Metadata 2
* @returns {boolean} true/false if series1 is newer than series2
*/
const compareSeriesDate = (series1, series2) => {
return (
series1._data.seriesDate > series2._data.seriesDate ||
(series1._data.seriesDate === series2._data.seriesDate &&
series1._data.seriesTime > series2._data.seriesTime)
);
};
export default findMostRecentStructuredReport;
@@ -0,0 +1,19 @@
/**
* Retrieve a list of all displaySets of all studies
*
* @param {Object} studies - List of studies loaded into the viewer
* @returns {Object} List of DisplaySets
*/
const getAllDisplaySets = studies => {
let allDisplaySets = [];
studies.forEach(study => {
if (study.getDisplaySets) {
allDisplaySets = allDisplaySets.concat(study.getDisplaySets());
}
});
return allDisplaySets;
};
export default getAllDisplaySets;
@@ -0,0 +1,14 @@
import * as dcmjs from 'dcmjs';
/**
* Checks if dcmjs has support to determined tool
*
* @param {string} toolName
* @returns {boolean}
*/
const isToolSupported = toolName => {
const adapter = dcmjs.adapters.Cornerstone;
return !!adapter[toolName];
};
export default isToolSupported;
+2 -2
View File
@@ -59,7 +59,7 @@ export class CommandsManager {
*
* @method
* @param {string} contextName - Namespace for commands
* @returs {Object} - the matched context
* @returns {Object} - the matched context
*/
getContext(contextName) {
const context = this.contexts[contextName];
@@ -161,7 +161,7 @@ export class CommandsManager {
return;
}
const { commandFn, storeContexts } = definition;
const { commandFn, storeContexts = [] } = definition;
const definitionOptions = definition.options;
let commandParams = {};
@@ -2,13 +2,14 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js';
export default class ExtensionManager {
constructor({ commandsManager, servicesManager }) {
constructor({ commandsManager, servicesManager, appConfig = {} }) {
this.modules = {};
this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES);
//
this._commandsManager = commandsManager;
this._servicesManager = servicesManager;
this._appConfig = appConfig;
this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = [];
@@ -26,7 +27,7 @@ export default class ExtensionManager {
const hasConfiguration = Array.isArray(extension);
if (hasConfiguration) {
const [ohifExtension, configuration] = extensions;
const [ohifExtension, configuration] = extension;
this.registerExtension(ohifExtension, configuration);
} else {
this.registerExtension(extension);
@@ -70,6 +71,7 @@ export default class ExtensionManager {
extension.preRegistration({
servicesManager: this._servicesManager,
commandsManager: this._commandsManager,
appConfig: this._appConfig,
configuration,
});
}
@@ -79,7 +81,8 @@ export default class ExtensionManager {
const extensionModule = this._getExtensionModule(
moduleType,
extension,
extensionId
extensionId,
configuration
);
if (extensionModule) {
@@ -102,7 +105,7 @@ export default class ExtensionManager {
* @param {Object} extension
* @param {string} extensionId - Used for logging warnings
*/
_getExtensionModule(moduleType, extension, extensionId) {
_getExtensionModule(moduleType, extension, extensionId, configuration) {
const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);
const getModuleFn = extension[getModuleFnName];
@@ -114,6 +117,8 @@ export default class ExtensionManager {
const extensionModule = getModuleFn({
servicesManager: this._servicesManager,
commandsManager: this._commandsManager,
appConfig: this._appConfig,
configuration,
});
if (!extensionModule) {
@@ -6,7 +6,7 @@ import log from './../log.js';
jest.mock('./../log.js');
describe('ExtensionManager.js', () => {
let extensionManager, commandsManager;
let extensionManager, commandsManager, servicesManager, appConfig;
beforeEach(() => {
commandsManager = {
@@ -14,7 +14,17 @@ describe('ExtensionManager.js', () => {
getContext: jest.fn(),
registerCommand: jest.fn(),
};
extensionManager = new ExtensionManager({ commandsManager });
servicesManager = {
registerService: jest.fn(),
};
appConfig = {
testing: true,
};
extensionManager = new ExtensionManager({
servicesManager,
commandsManager,
appConfig,
});
log.warn.mockClear();
jest.clearAllMocks();
});
@@ -37,6 +47,24 @@ describe('ExtensionManager.js', () => {
// Assert
expect(extensionManager.registerExtension.mock.calls.length).toBe(3);
});
it('calls registerExtension() for each extension passing its configuration if tuple', () => {
const fakeConfiguration = { testing: true };
extensionManager.registerExtension = jest.fn();
// SUT
const fakeExtensions = [
{ one: '1' },
[{ two: '2' }, fakeConfiguration],
{ three: '3 ' },
];
extensionManager.registerExtensions(fakeExtensions);
// Assert
expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual(
fakeConfiguration
);
});
});
describe('registerExtension()', () => {
@@ -49,19 +77,19 @@ describe('ExtensionManager.js', () => {
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
});
it('calls preRegistration() passing configuration along with servicesManager and commandsManager instances for extension', () => {
const configuration = { config: 'Some configuration' };
extensionManager._servicesManager = { services: { TestService: {} } };
it('calls preRegistration() passing dependencies and extension configuration to extension', () => {
const extensionConfiguration = { config: 'Some configuration' };
// SUT
const fakeExtension = { one: '1', preRegistration: jest.fn() };
extensionManager.registerExtension(fakeExtension, configuration);
const extension = { one: '1', preRegistration: jest.fn() };
extensionManager.registerExtension(extension, extensionConfiguration);
// Assert
expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({
servicesManager: extensionManager._servicesManager,
commandsManager: extensionManager._commandsManager,
configuration,
expect(extension.preRegistration.mock.calls[0][0]).toEqual({
servicesManager,
commandsManager,
appConfig,
configuration: extensionConfiguration,
});
});
@@ -135,8 +163,8 @@ describe('ExtensionManager.js', () => {
);
});
it('successfully passes a servicesManager and commandsManager instances to each module', () => {
extensionManager._servicesManager = { services: { TestService: {} } };
it('successfully passes dependencies to each module along with extension configuration', () => {
const extensionConfiguration = { testing: true };
const extension = {
id: 'hello-world',
@@ -147,11 +175,17 @@ describe('ExtensionManager.js', () => {
getCommandsModule: jest.fn(),
};
extensionManager.registerExtension(extension);
extensionManager.registerExtension(extension, extensionConfiguration);
expect(extension.getViewportModule.mock.calls[0][0]).toEqual({
servicesManager: extensionManager._servicesManager,
commandsManager: extensionManager._commandsManager,
Object.keys(extension).forEach(module => {
if (typeof extension[module] === 'function') {
expect(extension[module].mock.calls[0][0]).toEqual({
servicesManager,
commandsManager,
appConfig,
configuration: extensionConfiguration,
});
}
});
});
@@ -192,7 +226,7 @@ describe('ExtensionManager.js', () => {
return {
definitions: {
exampleDefinition: {
commandFn: () => {},
commandFn: () => { },
storeContexts: [],
options: {},
},
+12 -9
View File
@@ -5,6 +5,7 @@ import { ServicesManager } from './services';
import classes, { CommandsManager, HotkeysManager } from './classes/';
import DICOMWeb from './DICOMWeb';
import DICOMSR from './DICOMSR';
import cornerstone from './cornerstone.js';
import hangingProtocols from './hanging-protocols';
import header from './header.js';
@@ -20,9 +21,9 @@ import user from './user.js';
import utils from './utils/';
import {
createUINotificationService,
createUIModalService,
createUIDialogService,
UINotificationService,
UIModalService,
UIDialogService,
} from './services';
const OHIF = {
@@ -46,13 +47,14 @@ const OHIF = {
object,
log,
DICOMWeb,
DICOMSR,
viewer: {},
measurements,
hangingProtocols,
//
createUINotificationService,
createUIModalService,
createUIDialogService,
UINotificationService,
UIModalService,
UIDialogService,
};
export {
@@ -76,12 +78,13 @@ export {
object,
log,
DICOMWeb,
DICOMSR,
measurements,
hangingProtocols,
//
createUINotificationService,
createUIModalService,
createUIDialogService,
UINotificationService,
UIModalService,
UIDialogService,
};
export { OHIF };
+4 -3
View File
@@ -10,9 +10,9 @@ describe('Top level exports', () => {
'HotkeysManager',
'ServicesManager',
//
'createUINotificationService',
'createUIModalService',
'createUIDialogService',
'UINotificationService',
'UIModalService',
'UIDialogService',
//
'utils',
'studies',
@@ -28,6 +28,7 @@ describe('Top level exports', () => {
'object',
'log',
'DICOMWeb',
'DICOMSR',
'OHIF', //
'measurements',
'hangingProtocols',
@@ -1,4 +1,5 @@
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import log from '../../log';
import getLabel from '../lib/getLabel';
import getDescription from '../lib/getDescription';
@@ -7,13 +8,32 @@ import guid from '../../utils/guid';
import studyMetadataManager from '../../utils/studyMetadataManager';
import { measurementApiDefaultConfig } from './../configuration.js';
const configuration = {
...measurementApiDefaultConfig,
...measurementApiDefaultConfig
};
export default class MeasurementApi {
static Instance;
/**
* Set configuration: It should merge default configuration with any new one
*
* @static
* @param {Object} config
* @param {Object} config.server
* @param {string} config.server.type - The server type
* @param {string} config.server.wadoRoot - The server wado URL root
* @param {Array} config.measurementTools
* @param {string} config.measurementTools[].id - The tool group id
* @param {string} config.measurementTools[].name - The tool group name
* @param {Array} config.measurementTools[].childTools - The child tool's configuration
* @param {Object} config.dataExchange
* @param {Function} config.dataExchange.store - Function that store measurement data
* @param {Function} config.dataExchange.retrieve - Function that retrieves measurement data
*
* @memberof MeasurementApi
*/
static setConfiguration(config) {
Object.assign(configuration, config);
}
@@ -203,13 +223,14 @@ export default class MeasurementApi {
retrieveMeasurements(patientId, timepointIds) {
const retrievalFn = configuration.dataExchange.retrieve;
const { server } = configuration;
if (typeof retrievalFn !== 'function') {
log.error('Measurement retrieval function has not been configured.');
return;
}
return new Promise((resolve, reject) => {
retrievalFn(patientId, timepointIds).then(measurementData => {
retrievalFn(server).then(measurementData => {
if (measurementData) {
log.info('Measurement data retrieval');
log.info(measurementData);
@@ -230,6 +251,10 @@ export default class MeasurementApi {
// Synchronize the new tool data
this.syncMeasurementsAndToolData();
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
// Let others know that the measurements are updated
this.onMeasurementsUpdated();
}, reject);
@@ -237,6 +262,7 @@ export default class MeasurementApi {
}
storeMeasurements(timepointId) {
const { server } = configuration;
const storeFn = configuration.dataExchange.store;
if (typeof storeFn !== 'function') {
log.error('Measurement store function has not been configured.');
@@ -278,8 +304,9 @@ export default class MeasurementApi {
};
log.info('Saving Measurements for timepoints:', timepoints);
return storeFn(measurementData, filter).then(() => {
return storeFn(measurementData, filter, server).then(result => {
log.info('Measurement storage completed');
return result;
});
}
@@ -11,6 +11,16 @@ const servers = (state = defaultState, action) => {
servers.forEach(s => (s.active = true));
return { ...state, servers };
case 'ACTIVATE_SERVER': {
const newServer = { ...action.server, active: true };
const newServers = state.servers;
newServers.forEach(s => (s.active = false));
return {
...state,
servers: uniqBy([...newServers, newServer], 'wadoRoot'),
};
}
case 'SET_SERVERS':
return { ...state, servers: action.servers };
+83 -8
View File
@@ -11,20 +11,63 @@ import {
SET_VIEWPORT_LAYOUT_AND_DATA,
} from './../constants/ActionTypes.js';
const DEFAULT_STATE = {
export const DEFAULT_STATE = {
numRows: 1,
numColumns: 1,
activeViewportIndex: 0,
layout: {
viewports: [
{
// plugin: 'cornerstone',
},
],
viewports: [{}],
},
viewportSpecificData: {},
};
/**
* Take the new number of rows and columns, delete all not used viewport data and also set
* active viewport as default in case current one is not available anymore.
*
* @param {Number} numRows
* @param {Number} numColumns
* @param {Object} currentViewportSpecificData
* @returns
*/
const findActiveViewportSpecificData = (
numRows,
numColumns,
currentViewportSpecificData = {}
) => {
const numberOfViewports = numRows * numColumns;
const viewportSpecificData = cloneDeep(currentViewportSpecificData);
if (numberOfViewports < Object.keys(viewportSpecificData).length) {
Object.keys(viewportSpecificData).forEach(key => {
if (key > numberOfViewports - 1) {
delete viewportSpecificData[key];
}
});
}
return viewportSpecificData;
};
/**
* Take new number of rows and columns and make sure the current active viewport index is still available, if not, return the default
*
* @param {Number} numRows
* @param {Number} numColumns
* @param {Number} currentActiveViewportIndex
* @returns
*/
const getActiveViewportIndex = (
numRows,
numColumns,
currentActiveViewportIndex
) => {
const numberOfViewports = numRows * numColumns;
return currentActiveViewportIndex > numberOfViewports - 1
? DEFAULT_STATE.activeViewportIndex
: currentActiveViewportIndex;
};
/**
* The definition of a viewport action.
*
@@ -50,7 +93,12 @@ const viewports = (state = DEFAULT_STATE, action) => {
* @return {Object} New state.
*/
case SET_VIEWPORT_ACTIVE: {
return { ...state, activeViewportIndex: action.viewportIndex };
const activeViewportIndex = getActiveViewportIndex(
state.numRows,
state.numColumns,
action.viewportIndex
);
return { ...state, activeViewportIndex };
}
/**
@@ -59,11 +107,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
* @return {Object} New state.
*/
case SET_VIEWPORT_LAYOUT: {
const { numRows, numColumns } = action;
const viewportSpecificData = findActiveViewportSpecificData(
numRows,
numColumns,
state.viewportSpecificData
);
const activeViewportIndex = getActiveViewportIndex(
numRows,
numColumns,
state.activeViewportIndex
);
return {
...state,
numRows: action.numRows,
numColumns: action.numColumns,
layout: { viewports: [...action.viewports] },
viewportSpecificData,
activeViewportIndex,
};
}
@@ -73,12 +135,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
* @return {Object} New state.
*/
case SET_VIEWPORT_LAYOUT_AND_DATA: {
const { numRows, numColumns } = action;
const viewportSpecificData = findActiveViewportSpecificData(
numRows,
numColumns,
action.viewportSpecificData
);
const activeViewportIndex = getActiveViewportIndex(
numRows,
numColumns,
state.activeViewportIndex
);
return {
...state,
numRows: action.numRows,
numColumns: action.numColumns,
layout: { viewports: [...action.viewports] },
viewportSpecificData: cloneDeep(action.viewportSpecificData),
viewportSpecificData,
activeViewportIndex,
};
}
@@ -1,35 +1,61 @@
// https://medium.com/@netxm/testing-redux-reducers-with-jest-6653abbfe3e1
import reducer from './viewports.js';
import { Reducer } from 'redux-testkit';
import reducer, { DEFAULT_STATE } from './viewports.js';
import * as types from './../constants/ActionTypes.js';
describe('viewports reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({
activeViewportIndex: 0,
numRows: 1,
numColumns: 1,
layout: {
viewports: [{}],
},
viewportSpecificData: {},
});
expect(reducer(undefined, {})).toEqual(DEFAULT_STATE);
});
it('should handle SET_VIEWPORT_ACTIVE', () => {
const setViewportActiveAction = {
it('should handle SET_VIEWPORT_ACTIVE with inexistent viewport index', () => {
const initialState = {
numRows: 4,
numColumns: 4,
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT_ACTIVE,
viewportIndex: 100,
};
const updatedState = reducer({}, setViewportActiveAction);
const expectedToChangeInState = {
activeViewportIndex: 0,
};
expect(updatedState.activeViewportIndex).toEqual(
setViewportActiveAction.viewportIndex
);
Reducer(reducer)
.withState(initialState)
.expect(action)
.toChangeInState(expectedToChangeInState);
});
it('should handle SET_VIEWPORT_ACTIVE with existent viewport index', () => {
const initialState = {
numRows: 4,
numColumns: 4,
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT_ACTIVE,
viewportIndex: 5,
};
const expectedToChangeInState = {
activeViewportIndex: 5,
};
Reducer(reducer)
.withState(initialState)
.expect(action)
.toChangeInState(expectedToChangeInState);
});
it('should handle SET_VIEWPORT_LAYOUT', () => {
const setViewportLayoutAction = {
const initialState = DEFAULT_STATE;
const action = {
type: types.SET_VIEWPORT_LAYOUT,
numRows: 1,
numColumns: 2,
@@ -43,18 +69,193 @@ describe('viewports reducer', () => {
],
};
const updatedState = reducer({}, setViewportLayoutAction);
const expectedToChangeInState = {
numRows: 1,
numColumns: 2,
layout: {
viewports: [
{
plugin: 'cornerstone',
},
{
plugin: 'vtk',
},
],
},
};
expect(updatedState.numRows).toEqual(setViewportLayoutAction.numRows);
expect(updatedState.numColumns).toEqual(setViewportLayoutAction.numColumns);
expect(updatedState.layout.viewports).toEqual(
setViewportLayoutAction.viewports
);
Reducer(reducer)
.withState(initialState)
.expect(action)
.toChangeInState(expectedToChangeInState);
});
it('should handle SET_VIEWPORT_LAYOUT when we reduce the number of viewports', () => {
const initialState = {
numRows: 1,
numColumns: 2,
viewportSpecificData: {
0: { viewportData0: 'viewportData0' },
1: { viewportData1: 'viewportData1' },
},
layout: {
viewports: [],
},
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT_LAYOUT,
numRows: 1,
numColumns: 1,
viewports: [],
};
const expectedState = {
numRows: 1,
numColumns: 1,
viewportSpecificData: {
0: { viewportData0: 'viewportData0' },
},
layout: {
viewports: [],
},
activeViewportIndex: 0,
};
Reducer(reducer)
.withState(initialState)
.expect(action)
.toReturnState(expectedState);
});
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA', () => {
const initialState = {
numRows: 1,
numColumns: 1,
viewportSpecificData: {
0: { viewportData0: 'data0' },
},
layout: {
viewports: [{ plugin: 'cornerstone' }],
},
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
numRows: 1,
numColumns: 2,
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
viewportSpecificData: {
0: { viewportData0: 'NEWdata0' },
1: { viewportData1: 'NEWdata1' },
},
};
const expectedState = {
numRows: 1,
numColumns: 2,
viewportSpecificData: {
0: { viewportData0: 'NEWdata0' },
1: { viewportData1: 'NEWdata1' },
},
layout: {
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
},
activeViewportIndex: 0,
};
Reducer(reducer)
.withState(initialState)
.expect(action)
.toReturnState(expectedState);
});
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA when we reduce the number of viewports', () => {
const initialState = {
numRows: 1,
numColumns: 3,
viewportSpecificData: {
0: { viewportData0: 'vtkData0' },
1: { viewportData1: 'vtkData1' },
2: { viewportData2: 'vtkData2' },
},
layout: {
viewports: [{ plugin: 'vtk' }, { plugin: 'vtk' }, { plugin: 'vtk' }],
},
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
numRows: 1,
numColumns: 1,
viewports: [{ plugin: 'cornerstone' }],
viewportSpecificData: {
0: { viewportData0: 'cornerstoneData0' },
},
};
const expectedState = {
numRows: 1,
numColumns: 1,
viewportSpecificData: {
0: { viewportData0: 'cornerstoneData0' },
},
layout: {
viewports: [{ plugin: 'cornerstone' }],
},
activeViewportIndex: 0,
};
Reducer(reducer)
.withState(initialState)
.expect(action)
.toReturnState(expectedState);
});
it('should handle SET_VIEWPORT when we only set one viewport specific data', () => {
const initialState = {
numRows: 1,
numColumns: 2,
viewportSpecificData: {
0: { viewportData0: 'data0' },
1: { viewportData1: 'data1' },
},
layout: {
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
},
activeViewportIndex: 0,
};
const action = {
type: types.SET_VIEWPORT,
viewportIndex: 1,
viewportSpecificData: {
viewportData1: 'NEWdata1',
},
};
const expectedState = {
numRows: 1,
numColumns: 2,
viewportSpecificData: {
0: { viewportData0: 'data0' },
1: { viewportData1: 'NEWdata1' },
},
layout: {
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
},
activeViewportIndex: 0,
};
Reducer(reducer)
.withState(initialState)
.expect(action)
.toReturnState(expectedState);
});
// If there were previous keys, this would have
// "merge" behavior, not a clear & set
// May be worth another test?
it('should handle SET_VIEWPORT', () => {
const viewportToSet = 0;
const setViewportAction = {
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff. Show more