Compare commits

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

* Lazy initializing of CornerstoneWADIImageLoader - delayed till Viewer creation

* Async loading of react-vtkjs-viewport

* withCornerstone HOC to wrap routes requiring CornerstoneWADOImageLoader initiazation

* removed dead code in comments

* initWeWorkers is no more. Remove al its references.

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

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

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

* Fixed incorrect import from OHIFDicomPDFSopClassHandler to ConnectedOHIFDicomPDFViewer

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

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

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

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

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-25 13:53:47 -04:00
74 changed files with 1038 additions and 595 deletions

No files matched your search

+3
View File
@@ -18,6 +18,9 @@ exemptLabels:
- 'Triage :white_flag:'
- 'Extension: Discussion'
- 'Announcement 🎉'
- 'IDC:priority'
- 'IDC:candidate'
- 'IDC:collaboration'
# Label to use when marking an issue as stale
staleLabel: 'Stale :baguette_bread:'
# Comment to post when marking an issue as stale. Set to `false` to disable
+1 -1
View File
@@ -48,7 +48,7 @@ Check out these helpful links:
-->
<!-- prettier-ignore-start -->
[ohif-org]: https://www.ohif.org
[ohif-org]: http://www.ohif.org
[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb
[gh-issues]: https://github.com/OHIF/Viewers/issues
<!-- prettier-ignore-end -->
+1
View File
@@ -5,6 +5,7 @@
- [Getting Started](development/getting-started.md)
- [Contributing](development/contributing.md)
- [Continuous Integration](development/continous-integration.md)
- [Testing](development/testing.md)
- [Configuring](configuring/index.md)
- [Data Source](configuring/data-source.md)
+5 -10
View File
@@ -13,7 +13,6 @@ architected, and the role each of it's dependent libraries plays.
- [Overview](#overview)
- [Business Logic](#business-logic)
- [Component Library](#react-component-library)
- [Internationalization](#internationalization-i18n)
- [Extensions & Configuration](#extensions--configuration)
- [Common Questions](#common-questions)
@@ -74,10 +73,10 @@ This diagram is a conceptual illustration of how the Viewer is architected.
The [`@ohif/core`][core-github] project offers pre-packaged solutions for
features common to Web-based medical imaging viewers. For example:
- [Hotkeys](#)
- Hotkeys
- DICOM Web requests
- Hanging Protocols
- [Managing a study's measurements](../services/default/measurements.md)
- Managing a study's measurements
- Managing a study's DICOM metadata
- [A flexible pattern for extensions](../extensions/index.md)
- And many others
@@ -99,10 +98,6 @@ application.
[Check out our component library!](https://react.ohif.org/)
## Internationalization (i18n)
...
## Extensions & Configuration
While OHIF maintains several high value and commonly requested features in its
@@ -120,7 +115,7 @@ the viewer. Some common use cases include:
We expose common integration points via [extensions](../extensions/index.md) to
make this possible. The viewer and many of our own extensions also offer
[configuration][configuration]. For a list of extensions maintained by OHIF,
[check out this helpful table](../extensions/index.mdl#maintained-extensions).
[check out this helpful table](../extensions/index.md#maintained-extensions).
If you find yourself thinking "I wish the Viewer could do X", and you can't
accomplish it with an extension today, create a GitHub issue! We're actively
@@ -147,12 +142,12 @@ you would need to recreate.
-->
<!-- prettier-ignore-start -->
[monorepo]: #
[monorepo]: https://github.com/OHIF/Viewers/issues/768
[viewers-project]: https://github.com/OHIF/Viewers
[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer
[pwa]: https://developers.google.com/web/progressive-web-apps/
[configuration]: ../configuring/index.md
[extensions]: ../extensions/index.md
[core-github]: https://github.com/OHIF/viewers/platform/core
[ui-github]: https://github.com/OHIF/Viewers/platform/ui
[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui
<!-- prettier-ignore-end -->
+7 -7
View File
@@ -123,7 +123,7 @@ There are two important steps to making sure this setup works:
Most image archives do not provide either of these features "out of the box".
It's common to use IIS, Nginx, or Apache to route incoming requests and append
appropriate headers. You can find an example of this setup in our
[Nginx + Image Archive Deployment Recipe](deployment/recipes/nginx--image-archive.md).
[Nginx + Image Archive Deployment Recipe](./recipes/nginx--image-archive.md).
#### What if my archive doesn't support DicomWeb?
@@ -166,7 +166,7 @@ window.config = {
```
You can find an example of this setup in our
[User Account Control Deployment Recipe](deployment/recipes/user-account-control.md).
[User Account Control Deployment Recipe](./recipes/user-account-control.md).
#### Choosing a Flow for the Viewer
@@ -183,12 +183,12 @@ many possible configurations, so please don't feel limited to these setups.
Please feel free to suggest or contribute your own recipes.
- Script Include
- [Embedding the Viewer](deployment/recipes/embedded-viewer.md)
- [Embedding the Viewer](./recipes/embedded-viewer.md)
- Stand-Alone
- [Build for Production](deployment/recipes/build-for-production.md)
- [Static](deployment/recipes/static-assets.md)
- [Nginx + Image Archive](deployment/recipes/nginx--image-archive.md)
- [User Account Control](deployment/recipes/user-account-control.md)
- [Build for Production](./recipes/build-for-production.md)
- [Static](./recipes/static-assets.md)
- [Nginx + Image Archive](./recipes/nginx--image-archive.md)
- [User Account Control](./recipes/user-account-control.md)
<!--
Links
@@ -50,7 +50,7 @@ window.config = {
```
To learn more about how you can configure the OHIF Viewer, check out our
[Configuration Guide](./index.md).
[Configuration Guide](../../configuring/index.md).
<ol start="3"><li>
Render the viewer in the web page's target <code>div</code>
@@ -49,9 +49,7 @@ We can solve this one of two ways:
1. Have our Image Archive located at the same domain as our Web App
2. Add appropriate `Access-Control-Allow-*` HTTP headers
This solution uses the first approach, but you can see an example of the second
in the `docker-compose` bundled with this project for local development:
[HERE](https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/Nginx-Orthanc/docker-compose.yml)
**This solution uses the first approach.**
You can read more about CORS in this Medium article: [Understanding
CORS][understanding-cors]
@@ -121,7 +119,10 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc.js`
@@ -138,11 +139,11 @@ Viewer's configuration, you can run:
All other files are found in: `/docker/OpenResty-Orthanc/`
| Service | Configuration | Docs |
| ----------------- | ---------------------------------------------- | ------------------------------------------- |
| OHIF Viewer | [dockerfile][dockerfile] / [config.js][config] | You're reading them now! |
| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] |
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
| Service | Configuration | Docs |
| ----------------- | --------------------------------- | ------------------------------------------- |
| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! |
| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] |
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
## Next Steps
@@ -218,10 +219,11 @@ following resources helpful:
- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/)
- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/)
For a different take on this setup, check out the repository one of our
community members put together:
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
@@ -234,8 +236,7 @@ community members put together:
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[config]: #
[dockerfile]: #
[config-nginx]: #
[config-orthanc]: #
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json
<!-- prettier-ignore-end -->
@@ -122,7 +122,10 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js`
@@ -146,8 +149,8 @@ All other files are found in: `/docker/OpenResty-Orthanc-Keycloak/`
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
| Keycloak | [`/ohif-keycloak-realm.json`][config-keycloak]\* | |
- \* These are the seed values for Keycloak. They can be manually updated at
`http://127.0.0.1/auth/admin`
\* These are the seed values for Keycloak. They can be manually updated at
`http://127.0.0.1/auth/admin`
#### Keycloak Themeing
@@ -261,12 +264,12 @@ for OAuth:
- [Diagrams of OpenID Connect Flows](https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660)
- [KeyCloak: OpenID Connect Flows](https://www.keycloak.org/docs/latest/securing_apps/index.html#authorization-code)
- [Good description on SSO Protocols](https://www.keycloak.org/docs/2.5/server_admin/topics/sso-protocols/oidc.html)
For a different take on this setup, check out the repository one of our
community members put together:
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
@@ -278,8 +281,8 @@ community members put together:
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/src/config.js
[dockerfile]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-nginx]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-orthanc]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[config-keycloak]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/.recipes
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json
[config-keycloak]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json
<!-- prettier-ignore-end -->
+2 -2
View File
@@ -114,8 +114,8 @@ the tips above will help ensure your changes are reviewed.
## Testing contribution pull requests
OHIF uses [netlify](netlify.com) so that pull requests are autogenerated and
available for testing.
OHIF uses [netlify](https://www.netlify.com/) so that pull requests are
autogenerated and available for testing.
For example, [this url][example-url] allows you to test [pull request 237, the
request that created this FAQ entry,][pr-237] using data pulled from Amazon S3.
+1 -1
View File
@@ -132,7 +132,7 @@ feature `X` or scenario `Y`? Open an issue and let's discuss.
[flow-org]: https://flow.org/
<!-- Talks -->
[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw
[kent-talk]: https://www.youtube.com/watch?list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf
[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE
[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk
[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests
<!-- Images -->
@@ -31,6 +31,7 @@
</td>
<td>Viewport, Toolbar</td>
</tr>
<!-- dicom-html -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-html">DICOM HTML</a>
@@ -40,6 +41,7 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-pdf -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-pdf">DICOM PDF</a>
@@ -49,6 +51,7 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-microscopy -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-microscopy">DICOM Microscopy</a>
@@ -58,5 +61,15 @@
</td>
<td>Viewport, SopClassHandler</td>
</tr>
<!-- dicom-segmentation -->
<tr>
<td>
<a href="https://www.npmjs.com/package/@ohif/extension-dicom-segmentation">DICOM Segmentation</a>
</td>
<td>
Renders segmentation images for a <a href="https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7">specific SopClassUID</a>.
</td>
<td>Panel, Toolbar</td>
</tr>
</tbody>
</table>
+11 -9
View File
@@ -74,7 +74,8 @@ export default {
### Registering an Extension
There are two different ways to register and configure extensions: At
[runtime](#runtime-extensions) and at [build time](#bundled-extensions).
[runtime](#registering-at-runtime) and at
[build time](#registering-at-build-time).
You can leverage one or both strategies. Which one(s) you choose depend on your
application's requirements. Each [module](#modules) defined by the extension
@@ -82,9 +83,9 @@ becomes available to the core application via the `ExtensionManager`.
#### Registering at Runtime
The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for
that file includes an `Extensions` key that supports an array of extensions to
register.
The `@ohif/viewer` uses a [configuration file](../viewer/configuration.md) at
startup. The schema for that file includes an `Extensions` key that supports an
array of extensions to register.
```js
// prettier-ignore
@@ -193,8 +194,8 @@ today, create a GitHub issue!
The `ExtensionManager` is a class made available to us via the `@ohif/core`
project (platform/core). Our application instantiates a single instance of it,
and provides a `ServicesManager` and `CommandsManager` along with the application's
configuration through the appConfig key (optional).
and provides a `ServicesManager` and `CommandsManager` along with the
application's configuration through the appConfig key (optional).
```js
const commandsManager = new CommandsManager();
@@ -202,7 +203,7 @@ const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
appConfig
appConfig,
});
```
@@ -212,8 +213,9 @@ The `ExtensionManager` only has a few public members:
- `registerExtensions` - Registers an array of extensions
- `modules` - An object containing registered extensions by `MODULE_TYPE`
During registration, lifecycle hooks and modules have access to the extension's config,
the application's config and `ExtensionManager`'s `ServicesManager` and `CommandsManager` instances.
During registration, lifecycle hooks and modules have access to the extension's
config, the application's config and `ExtensionManager`'s `ServicesManager` and
`CommandsManager` instances.
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
appropriate places in our application.
@@ -1,9 +1,9 @@
# Module: SOP Class Handler
An extension can register a SOP Class Handler Module by defining a
`getSopClassHandlerModule` method. The SOP Class Handler is a bit different from
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
own components. It instead defines:
An extension can register a [SOP Class][sop-class-link] Handler Module by
defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]
Handler is a bit different from the other modules, as it doesn't provide a `1:1`
schema for UI or provide it's own components. It instead defines:
- `sopClassUIDs`: an array of string SOP Class UIDs that the
`getDisplaySetFromSeries` method should be applied to.
@@ -76,6 +76,13 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) {
};
```
### More examples :
- [Dicom-HTML SOP][dicom-html-sop]
- [Dicom-PDF SOP][dicom-pdf-sop]
- [Dicom-Microscopy SOP][dicom-micro-sop]
- [Dicom-Segmentation SOP][dicom-seg-sop]
## `@ohif/viewer` usage
We use the `sopClassHandlerModule`s in three different places:
@@ -88,3 +95,11 @@ Each time, it is used to map study and series data to `displaySets`. It does
this by working alongside the `StudyMetadataManager` in `@ohif/core`. That
manager has the method `createDisplaySets` that takes an array of
`sopClassHandlerModules`.
<!-- prettier-ignore-start -->
[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html
[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12
[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6
[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7
[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7
<!-- prettier-ignore-end -->
+2 -2
View File
@@ -75,7 +75,7 @@ items to our ["Active Development" GitHub Project Board][gh-board]. Items on
this project board are either in active development by Core Team members, or
queued up for development as in-progress items are completed.
> :pencil: Want to contribute but not sure where to start? Check out [Up for
> 🖋 Want to contribute but not sure where to start? Check out [Up for
> grabs][label-grabs] issues and our [Contributing
> documentation][contributing-docs]
@@ -156,5 +156,5 @@ addressed and a Stable version is released.
[ohif-prod]: https://viewer.ohif.org
[gh-board]: https://github.com/OHIF/Viewers/projects/4
[label-grabs]: https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs+%3Araising_hand_woman%3A%22
[contributing-docs]: https://docs.ohif.org/contributing/
[contributing-docs]: ./development/contributing.md
<!-- prettier-ignore-end -->
+1 -1
View File
@@ -62,7 +62,7 @@ able to fail or be removed. Related patterns that may reduce coupling include:
-->
<!-- prettier-ignore-start -->
[commands]: #/
[commands]: ../extensions/modules/commands.md
[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services
[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js
[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern
+2 -2
View File
@@ -2,8 +2,8 @@
- [Overview](#overview)
- [Example](#example)
- [Tips & Tricks](#/)
- [Maintained Services](#/)
- [Tips & Tricks](#tips--tricks)
- [Maintained Services](#maintained-services)
## Overview
+4 -4
View File
@@ -135,7 +135,7 @@ console.log(T('$t(Common:Play) my translated text'));
# Main Concepts While Translating
## - Namespaces
## Namespaces
Namespaces are being used to organize translations in smaller portions, combined
semantically or by use. Each `.json` file inside `@ohif/i18n` npm package
@@ -157,7 +157,7 @@ NameSpace, like this following example getting data from `Common` NameSpace:
$t(Common:Reset)
```
## - Extending Languages in @ohif/i18n
## Extending Languages in @ohif/i18n
Sometimes, even using the same language, some nouns or jargons can change
according to the country, states or even from Hospital to Hospital.
@@ -212,7 +212,7 @@ object like this:
Please check the `index.js` files inside locales folder for an example of this
exporting structure.
### - Extending languages dynamically
### Extending languages dynamically
You have access to the i18next instance, so you can use the
[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init)
@@ -314,4 +314,4 @@ REACT_APP_I18N_DEBUG=true yarn run dev
### Contributing with new languages
Contributions of any kind are welcome! Please check the
[instructions](https://docs.ohif.org/contributing.html).
[instructions](../development/contributing.md).
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.0...@ohif/extension-cornerstone@2.5.1) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-cornerstone
# [2.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.3...@ohif/extension-cornerstone@2.5.0) (2020-03-13)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.5.0",
"version": "2.5.1",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+12 -6
View File
@@ -1,14 +1,20 @@
import React from 'react';
import init from './init.js';
import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
const OHIFCornerstoneViewport = asyncComponent(() =>
import(
/* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'
)
);
const Component = React.lazy(() => {
return import('./OHIFCornerstoneViewport');
});
const OHIFCornerstoneViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
/**
*
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.1...@ohif/extension-dicom-html@1.1.2) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-html
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.0...@ohif/extension-dicom-html@1.1.1) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-html
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "1.1.1",
"version": "1.1.2",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+12 -6
View File
@@ -1,11 +1,17 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
const OHIFDicomHtmlViewport = asyncComponent(() =>
import(
/* webpackChunkName: "OHIFDicomHtmlViewport" */ './OHIFDicomHtmlViewport.js'
)
);
const Component = React.lazy(() => {
return import('./OHIFDicomHtmlViewport');
});
const OHIFDicomHtmlViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.7...@ohif/extension-dicom-microscopy@0.50.8) (2020-03-31)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.6...@ohif/extension-dicom-microscopy@0.50.7) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.50.7",
"version": "0.50.9",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -34,11 +34,12 @@ class DicomMicroscopyViewport extends Component {
.then(instances => {
const promises = [];
for (let i = 0; i < instances.length; i++) {
const sopInstanceUid = instances[i]['00080018']['Value'][0];
const sopInstanceUID = instances[i]['00080018']['Value'][0];
const retrieveInstanceOptions = {
studyInstanceUID: displaySet.StudyInstanceUID,
seriesInstanceUID: displaySet.SeriesInstanceUID,
sopInstanceUid,
sopInstanceUID,
};
const promise = dicomWebClient
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+12 -6
View File
@@ -1,11 +1,17 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
const DicomMicroscopyViewport = asyncComponent(() =>
import(
/* webpackChunkName: "DicomMicroscopyViewport" */ './DicomMicroscopyViewport.js'
)
);
const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
});
const DicomMicroscopyViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.1...@ohif/extension-dicom-pdf@1.0.2) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-pdf
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "1.0.2",
"version": "1.0.3",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
@@ -1,37 +0,0 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+12 -6
View File
@@ -1,11 +1,17 @@
import asyncComponent from './asyncComponent.js';
import React from 'react';
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
const ConnectedOHIFDicomPDFViewer = asyncComponent(() =>
import(
/* webpackChunkName: "ConnectedOHIFDicomPDFViewer" */ './ConnectedOHIFDicomPDFViewer'
)
);
const Component = React.lazy(() => {
return import('./ConnectedOHIFDicomPDFViewer');
});
const ConnectedOHIFDicomPDFViewer = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default {
/**
@@ -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.
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.0...@ohif/extension-dicom-segmentation@0.1.1) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
# 0.1.0 (2020-03-13)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.1.0",
"version": "0.1.1",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -14,7 +14,7 @@ const OHIFDicomSegSopClassHandler = {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
getDisplaySetFromSeries: function (
series,
study,
dicomWebClient,
@@ -53,7 +53,7 @@ const OHIFDicomSegSopClassHandler = {
SeriesDescription,
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
segDisplaySet.load = function (referencedDisplaySet, studies) {
return loadSegmentation(
segDisplaySet,
referencedDisplaySet,
@@ -39,50 +39,26 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
* TODO: wrap get/set interactions with the cornerstoneTools
* store with context to make these kind of things less blurry.
*/
const segmentationModule = cornerstoneTools.getModule('segmentation');
const { configuration } = segmentationModule;
const { configuration } = cornerstoneTools.getModule('segmentation');
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
const [brushRadius, setBrushRadius] = useState(DEFAULT_BRUSH_RADIUS);
/* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
const [brushColor, setBrushColor] = useState('rgba(221, 85, 85, 1)');
const [selectedSegment, setSelectedSegment] = useState();
const [showSegSettings, setShowSegSettings] = useState(false);
const [selectedSegmentation, setSelectedSegmentation] = useState();
const viewport = viewports[activeIndex];
const {
StudyInstanceUID,
SeriesInstanceUID,
displaySetInstanceUID,
} = viewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const firstImageId = studyMetadata.getFirstImageId(displaySetInstanceUID);
/* CornerstoneTools */
const [brushStackState, setBrushStackState] = useState(
segmentationModule.state.series[firstImageId]
);
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)', /* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
selectedSegment: null,
selectedSegmentation: null,
showSegSettings: false,
brushStackState: null,
labelmapList: [],
segmentList: []
});
useEffect(() => {
setShowSegSettings(showSegSettings && !isOpen);
}, [isOpen]);
useEffect(() => {
setBrushStackState(segmentationModule.state.series[firstImageId]);
}, [studies, viewports, activeIndex, firstImageId]);
useEffect(() => {
if (brushStackState) {
setSelectedSegmentation(brushStackState.activeLabelmapIndex);
}
const labelmapModifiedHandler = event => {
log.warn('labelmap modified', event);
setBrushStackState(segmentationModule.state.series[firstImageId]);
log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
updateState('brushStackState', module.state.series[firstImageId]);
};
/*
@@ -107,27 +83,43 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
};
});
if (!brushStackState) {
return null;
}
useEffect(() => {
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
const brushStackState = module.state.series[firstImageId];
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
if (brushStackState) {
const labelmap3D = brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const labelmapList = getLabelmapList(brushStackState, firstImageId, activeViewport);
const segmentList = getSegmentList(labelmap3D, firstImageId);
setState(state => ({
...state,
brushStackState,
selectedSegmentation: brushStackState.activeLabelmapIndex,
labelmapList,
segmentList
}));
} else {
setState(state => ({
...state,
labelmapList: [],
segmentList: [],
}));
}
}, [studies, viewports, activeIndex]);
/*
* 2. UseEffect to update state? or to a least trigger a re-render
* 4. Toggle visibility of labelmap?
* 5. Toggle visibility of seg?
*
* If the port is cornerstone, just need to call a re-render.
* If the port is vtkjs, its a bit more tricky as we now need to create a new
*/
/* Handle open/closed panel behaviour */
useEffect(() => {
updateState('showSegSettings', state.showSegSettings && !isOpen);
}, [isOpen]);
const getLabelmapList = () => {
const getLabelmapList = (brushStackState, firstImageId, activeViewport) => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
StudyInstanceUID,
SeriesInstanceUID
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
return referencedSegDisplaysets.map((displaySet, index) => {
@@ -148,23 +140,19 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await _setActiveLabelmap(
viewport,
activeViewport,
studies,
displaySet,
firstImageId,
brushStackState.activeLabelmapIndex
);
setSelectedSegmentation(activatedLabelmapIndex);
updateState('selectedSegmentation', activatedLabelmapIndex);
},
};
});
};
const labelmapList = getLabelmapList();
const segmentList = [];
if (labelmap3D) {
const getSegmentList = (labelmap3D, firstImageId) => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
@@ -186,10 +174,12 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
}, [])
.sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
@@ -207,14 +197,14 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
}
}
const sameSegment = selectedSegment === segmentNumber;
const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
);
setSelectedSegment(sameSegment ? null : segmentNumber);
updateState('selectedSegment', sameSegment ? null : segmentNumber);
};
segmentList.push(
@@ -229,20 +219,27 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
);
}
return segmentList;
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
}
};
const updateState = (field, value) => {
setState(state => ({ ...state, [field]: value }));
};
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
setBrushRadius(updatedRadius);
segmentationModule.setters.radius(updatedRadius);
updateState('brushRadius', updatedRadius);
const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius);
}
};
@@ -251,29 +248,30 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
if (labelmap3D.activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--;
}
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const incrementSegment = event => {
event.preventDefault();
labelmap3D.activeSegmentIndex++;
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
setBrushColor(color);
updateState('brushColor', color);
};
const getActiveSegmentColor = () => {
if (!brushStackState) {
if (!state.brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`;
@@ -293,11 +291,11 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
refreshViewport();
};
if (showSegSettings) {
if (state.showSegSettings) {
return (
<SegmentationSettings
configuration={configuration}
onBack={() => setShowSegSettings(false)}
onBack={() => updateState('showSegSettings', false)}
onChange={updateConfiguration}
/>
);
@@ -309,13 +307,13 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
name="cog"
width="25px"
height="25px"
onClick={() => setShowSegSettings(true)}
onClick={() => updateState('showSegSettings', true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
index={labelmap3D.activeSegmentIndex}
index={state.selectedSegment}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
@@ -331,17 +329,17 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
<div className="segmentations">
<SegmentationSelect
value={
labelmapList.find(i => i.value === selectedSegmentation) || null
state.labelmapList.find(i => i.value === state.selectedSegmentation) || null
}
formatOptionLabel={SegmentationItem}
options={labelmapList}
options={state.labelmapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={segmentList.length} />}
customHeader={<SegmentsHeader count={state.segmentList.length} />}
>
{segmentList}
{state.segmentList}
</TableList>
</ScrollableArea>
</div>
@@ -420,7 +418,7 @@ const _setActiveLabelmap = async (
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return;
return displaySet.labelmapIndex;
}
if (!displaySet.isLoaded) {
@@ -431,7 +429,6 @@ const _setActiveLabelmap = async (
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewport();
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.7...@ohif/extension-vtk@1.3.8) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.6...@ohif/extension-vtk@1.3.7) (2020-04-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.5...@ohif/extension-vtk@1.3.6) (2020-04-01)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.4...@ohif/extension-vtk@1.3.5) (2020-03-25)
**Note:** Version bump only for package @ohif/extension-vtk
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.3.5",
"version": "1.3.8",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -53,7 +53,7 @@
"react-vtkjs-viewport": "^0.8.3"
},
"devDependencies": {
"@ohif/core": "^2.6.4",
"@ohif/core": "^2.6.7",
"@ohif/ui": "^1.4.1",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
+22 -14
View File
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
import { getReactVtkjsViewport } from './utils/getReactVtkjsViewport';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import LoadingIndicator from './LoadingIndicator.js';
import OHIF from '@ohif/core';
@@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
};
static defaultProps = {
onScroll: () => {},
onScroll: () => { },
};
static id = 'OHIFVTKViewport';
@@ -133,7 +133,8 @@ class OHIFVTKViewport extends Component {
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex
frameIndex,
reactVtkjsViewport
) => {
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
@@ -144,7 +145,10 @@ class OHIFVTKViewport extends Component {
frameIndex
);
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
const imageDataObject = reactVtkjsViewport.getImageData(
stack.imageIds,
displaySetInstanceUID
);
let labelmapDataObject;
let labelmapColorLUT;
@@ -257,7 +261,9 @@ class OHIFVTKViewport extends Component {
return volumeActor;
}
setStateFromProps() {
async setStateFromProps() {
const reactVtkjsViewport = await getReactVtkjsViewport();
const { studies, displaySet } = this.props.viewportData;
const {
StudyInstanceUID,
@@ -295,8 +301,10 @@ class OHIFVTKViewport extends Component {
studies,
StudyInstanceUID,
displaySetInstanceUID,
sopClassUIDs[0],
SOPInstanceUID,
frameIndex
frameIndex,
reactVtkjsViewport
);
this.imageDataObject = imageDataObject;
@@ -317,7 +325,7 @@ class OHIFVTKViewport extends Component {
dataDetails,
},
() => {
this.loadProgressively(imageDataObject);
this.loadProgressively(imageDataObject, reactVtkjsViewport);
// TODO: There must be a better way to do this.
// We do this so that if all the data is available the react-vtkjs-viewport
@@ -335,26 +343,26 @@ class OHIFVTKViewport extends Component {
);
}
componentDidMount() {
this.setStateFromProps();
async componentDidMount() {
await this.setStateFromProps();
}
componentDidUpdate(prevProps) {
async componentDidUpdate(prevProps) {
const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
this.setStateFromProps();
await this.setStateFromProps();
}
}
loadProgressively(imageDataObject) {
loadImageData(imageDataObject);
loadProgressively(imageDataObject, reactVtkjsViewport) {
reactVtkjsViewport.loadImageData(imageDataObject);
const { isLoading, insertPixelDataPromises } = imageDataObject;
+9 -2
View File
@@ -1,9 +1,16 @@
import React, { useEffect, useCallback } from 'react';
import { View2D } from 'react-vtkjs-viewport';
// import { View2D } from 'react-vtkjs-viewport';
import PropTypes from 'prop-types';
import asyncComponent from './asyncComponent.js';
import { getReactVtkjsViewport } from './utils/getReactVtkjsViewport';
import './VTKViewport.css';
const View2D = asyncComponent(async () => {
const reactVtkjsViewport = await getReactVtkjsViewport();
return { default: reactVtkjsViewport.View2D };
});
const VTKViewport = props => {
const style = { width: '100%', height: '100%', position: 'relative' };
@@ -52,7 +59,7 @@ VTKViewport.propTypes = {
};
VTKViewport.defaultProps = {
onScroll: () => {},
onScroll: () => { },
};
export default VTKViewport;
+13 -14
View File
@@ -1,10 +1,5 @@
import throttle from 'lodash.throttle';
import {
vtkInteractorStyleMPRCrosshairs,
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRRotate,
vtkSVGCrosshairsWidget,
} from 'react-vtkjs-viewport';
import { getReactVtkjsViewport } from './utils/getReactVtkjsViewport';
import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js';
@@ -124,16 +119,18 @@ const commandsModule = ({ commandsManager }) => {
_setView(api, [0, 1, 0], [0, 0, 1]);
},
enableRotateTool: () => {
enableRotateTool: async () => {
const reactVtkjsViewport = await getReactVtkjsViewport();
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRRotate.newInstance();
const istyle = reactVtkjsViewport.vtkInteractorStyleMPRRotate.newInstance();
api.setInteractorStyle({ istyle });
});
},
enableCrosshairsTool: () => {
enableCrosshairsTool: async () => {
const reactVtkjsViewport = await getReactVtkjsViewport();
apis.forEach((api, apiIndex) => {
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
const istyle = reactVtkjsViewport.vtkInteractorStyleMPRCrosshairs.newInstance();
api.setInteractorStyle({
istyle,
@@ -141,7 +138,7 @@ const commandsModule = ({ commandsManager }) => {
});
});
},
enableLevelTool: () => {
enableLevelTool: async () => {
function updateVOI(apis, windowWidth, windowCenter) {
apis.forEach(api => {
api.updateVOI(windowWidth, windowCenter);
@@ -162,8 +159,9 @@ const commandsModule = ({ commandsManager }) => {
},
};
const reactVtkjsViewport = await getReactVtkjsViewport();
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
const istyle = reactVtkjsViewport.vtkInteractorStyleMPRWindowLevel.newInstance();
api.setInteractorStyle({ istyle, callbacks });
});
@@ -260,14 +258,15 @@ const commandsModule = ({ commandsManager }) => {
}
// Add widgets and set default interactorStyle of each viewport.
const reactVtkjsViewport = await getReactVtkjsViewport();
apis.forEach((api, apiIndex) => {
api.addSVGWidget(
vtkSVGCrosshairsWidget.newInstance(),
reactVtkjsViewport.vtkSVGCrosshairsWidget.newInstance(),
'crosshairsWidget'
);
const uid = api.uid;
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
const istyle = reactVtkjsViewport.vtkInteractorStyleMPRCrosshairs.newInstance();
api.setInteractorStyle({
istyle,
@@ -0,0 +1,5 @@
export function getReactVtkjsViewport() {
return import(
/* webpackChunkName: "ReactVtkjsViewport" */ 'react-vtkjs-viewport'
);
}
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.6.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.6...@ohif/core@2.6.7) (2020-04-02)
### Bug Fixes
* 🐛 Fix multiframe images ([#1595](https://github.com/OHIF/Viewers/issues/1595)) ([9e0bd52](https://github.com/OHIF/Viewers/commit/9e0bd52c6a86648eb6673344a8555ad787043e5c))
## [2.6.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.5...@ohif/core@2.6.6) (2020-04-02)
**Note:** Version bump only for package @ohif/core
## [2.6.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.4...@ohif/core@2.6.5) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.3...@ohif/core@2.6.4) (2020-03-25)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.6.4",
"version": "2.6.7",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -1,10 +1,10 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import {
clearStudyLoadingProgress,
setStudyLoadingProgress,
} from '../redux/actions';
import StackManager from '../utils/StackManager';
import { getCornerstoneWADOImageLoader } from '../utils/cornerstoneWADOImageLoader';
class BaseLoadingListener {
constructor(stack, options = {}) {
@@ -103,7 +103,8 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
this._checkCachedData();
}
_checkCachedData() {
async _checkCachedData() {
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
this._dataSetUrl
);
@@ -208,20 +208,21 @@ export class StudyMetadata extends Metadata {
/**
* Returns a list of derived datasets in the study, filtered by the given filter.
* @param {object} filter An object containing search filters
* @param {object} filter.modality
* @param {object} filter.Modality
* @param {object} filter.referencedSeriesInstanceUID
* @param {object} filter.referencedFrameOfReferenceUID
* @return {Array} filtered derived display sets
*/
getDerivedDatasets(filter) {
const {
modality,
Modality,
referencedSeriesInstanceUID,
referencedFrameOfReferenceUID,
} = filter;
let filteredDerivedDisplaySets = this._derivedDisplaySets;
if (modality) {
if (Modality) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet.Modality === Modality
);
@@ -1,8 +1,8 @@
import DICOMWeb from '../../../DICOMWeb';
import metadataProvider from '../../../classes/MetadataProvider';
import getWADORSImageId from '../../../utils/getWADORSImageId';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import getReferencedSeriesSequence from './getReferencedSeriesSequence';
import { getCornerstoneWADOImageLoader } from '../../../utils/cornerstoneWADOImageLoader';
/**
* Create a plain JS object that describes a study (a study descriptor object)
@@ -160,6 +160,7 @@ async function makeSOPInstance(server, study, instance) {
const wadoRSMetadata = Object.assign(instance);
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
if (sopInstance.NumberOfFrames) {
for (let i = 0; i < sopInstance.NumberOfFrames; i++) {
const wadorsImageId = getWADORSImageId(sopInstance, i);
+2 -3
View File
@@ -35,7 +35,8 @@ function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) {
imageIndex: imageIndex + 1,
};
const NumberOfFrames = image.NumberOfFrames;
const naturalizedInstance = instance.getData().metadata;
const NumberOfFrames = naturalizedInstance.NumberOfFrames;
if (NumberOfFrames > 1) {
for (let i = 0; i < NumberOfFrames; i++) {
@@ -60,8 +61,6 @@ function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) {
imageId = getImageId(image);
imageIds.push(imageId);
const naturalizedInstance = instance.getData().metadata;
const {
StudyInstanceUID,
SeriesInstanceUID,
@@ -0,0 +1,5 @@
export function getCornerstoneWADOImageLoader() {
return import(
/* webpackChunkName: "CornerstoneWADOImageLoader" */ 'cornerstone-wado-image-loader'
);
}
@@ -1,7 +1,7 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb';
import { getCornerstoneWADOImageLoader } from './cornerstoneWADOImageLoader';
const getImageId = imageObj => {
if (!imageObj) {
@@ -86,7 +86,7 @@ const getImageLoaderType = imageId => {
};
class DicomLoaderService {
getLocalData(dataset, studies) {
async getLocalData(dataset, studies) {
if (dataset && dataset.localFile) {
// Use referenced imageInstance
const imageInstance = getImageInstance(dataset);
@@ -98,6 +98,7 @@ class DicomLoaderService {
}
if (!someInvalidStrings(imageId)) {
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
}
@@ -176,16 +177,16 @@ class DicomLoaderService {
}
}
*getLoaderIterator(dataset, studies) {
yield this.getLocalData(dataset, studies);
async *getLoaderIterator(dataset, studies) {
yield await this.getLocalData(dataset, studies);
yield this.getDataByImageType(dataset);
yield this.getDataByDatasetType(dataset);
}
findDicomDataPromise(dataset, studies) {
async findDicomDataPromise(dataset, studies) {
const loaderIterator = this.getLoaderIterator(dataset, studies);
// it returns first valid retriever method.
for (const loader of loaderIterator) {
for await (const loader of loaderIterator) {
if (loader) {
return loader;
}
+73
View File
@@ -3,6 +3,79 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.8.16](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.15...@ohif/viewer@3.8.16) (2020-04-02)
**Note:** Version bump only for package @ohif/viewer
## [3.8.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.14...@ohif/viewer@3.8.15) (2020-04-02)
**Note:** Version bump only for package @ohif/viewer
## [3.8.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.13...@ohif/viewer@3.8.14) (2020-04-02)
**Note:** Version bump only for package @ohif/viewer
## [3.8.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.12...@ohif/viewer@3.8.13) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
## [3.8.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.11...@ohif/viewer@3.8.12) (2020-03-31)
**Note:** Version bump only for package @ohif/viewer
## [3.8.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.10...@ohif/viewer@3.8.11) (2020-03-26)
### Bug Fixes
* [#1312](https://github.com/OHIF/Viewers/issues/1312) Cine dialog remains on screen ([#1540](https://github.com/OHIF/Viewers/issues/1540)) ([7d22bb7](https://github.com/OHIF/Viewers/commit/7d22bb7d5a8590cffc169725c93942f758fe13a0))
## [3.8.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.9...@ohif/viewer@3.8.10) (2020-03-26)
**Note:** Version bump only for package @ohif/viewer
## [3.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.8...@ohif/viewer@3.8.9) (2020-03-25)
### Bug Fixes
* Load measurement in active viewport. ([#1558](https://github.com/OHIF/Viewers/issues/1558)) ([99022f2](https://github.com/OHIF/Viewers/commit/99022f2bac752f3cd1cedb61e222b8d411e158c8))
## [3.8.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.7...@ohif/viewer@3.8.8) (2020-03-25)
**Note:** Version bump only for package @ohif/viewer
@@ -7,15 +7,22 @@ describe('OHIF User Preferences', () => {
beforeEach(() => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
});
it('checks displayed information on User Preferences modal', function() {
cy.initPreferencesModalAliases();
//Check Title
cy.get('@preferencesModal').should('contain.text', 'User Preferences');
//Check tabs
cy.get('@userPreferencesHotkeysTab')
.should('have.text', 'Hotkeys')
.and('have.class', 'active');
cy.get('@userPreferencesGeneralTab').should('have.text', 'General');
cy.get('@userPreferencesWindowLevelTab').should(
'have.text',
'Window Level'
);
//Check buttons
cy.get('@restoreBtn')
.scrollIntoView()
.should('have.text', 'Reset to Defaults');
@@ -26,8 +33,7 @@ describe('OHIF User Preferences', () => {
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -92,16 +98,22 @@ describe('OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
cy.get('@restoreBtn')
.scrollIntoView()
.click();
// Save
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Header should be in "English (USA)"
cy.get('.research-use')
@@ -124,30 +136,54 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="options-menu"]').click();
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
//Check table header
cy.get('.wlRow.header')
.should('contains.text', 'Preset')
.and('contains.text', 'Description')
.and('contains.text', 'Window')
.and('contains.text', 'Level');
//Check table has more than 1 row (more than header)
cy.get('.wlRow')
.its('length')
.should('be.greaterThan', 1);
});
it('checks if Preferences set in Study List Page will be consistent on Viewer Page', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{shift}Q');
// Save new hotkey
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Open User Preferences modal again
cy.openPreferences();
// Go to General tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Set language to Spanish
cy.setLanguage('Spanish');
// Go to Study Viewer page
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.initCommonElementsAliases();
@@ -178,7 +214,9 @@ describe('OHIF User Preferences', () => {
context('Study Viewer Page', function() {
before(() => {
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -186,29 +224,40 @@ describe('OHIF User Preferences', () => {
cy.initCommonElementsAliases();
cy.resetViewport();
cy.resetUserHoktkeyPreferences();
cy.resetUserHotkeyPreferences();
cy.resetUserGeneralPreferences();
// Open User Preferences modal
cy.openPreferences();
});
afterEach(() => {
// Close User Preferences Modal (if displayed)
cy.get('body').then(body => {
if (body.find('.OHIFModal__header').length > 0) {
cy.get('[data-cy="close-button"]').click({ force: true });
}
});
});
it('checks displayed information on User Preferences modal', function() {
cy.get('@preferencesModal').should('contain.text', 'User Preferences');
cy.get('@userPreferencesHotkeysTab')
.should('have.text', 'Hotkeys')
.and('have.class', 'active');
cy.get('@userPreferencesGeneralTab').should('have.text', 'General');
cy.get('@userPreferencesWindowLevelTab').should(
'have.text',
'Window Level'
);
cy.get('@restoreBtn')
.scrollIntoView()
.should('have.text', 'Reset to Defaults');
cy.get('@cancelBtn').should('have.text', 'Cancel');
cy.get('@saveBtn').should('have.text', 'Save');
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -232,9 +281,6 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferencias');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks if user can cancel the language selection and application will be in "English (USA)"', function() {
@@ -260,13 +306,10 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferences');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks if user can restore to default the language selection and application will be in "English (USA)', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -278,12 +321,14 @@ describe('OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// click on restore button
cy.get('@restoreBtn')
.scrollIntoView()
.click();
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
@@ -303,15 +348,11 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferences');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks new hotkeys for "Rotate Right" and "Rotate Left"', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -324,10 +365,16 @@ describe('OHIF User Preferences', () => {
'{shift}{leftarrow}'
);
//Save new hotkeys
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
//Rotate Right with new Hotkey
cy.get('body').type('{shift}{rightarrow}');
@@ -340,8 +387,8 @@ describe('OHIF User Preferences', () => {
it('checks new hotkeys for "Next" and "Previous" Image on Viewport', function() {
// Update hotkeys for 'Next/Previous Viewport'
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.setNewHotkeyShortcutOnUserPreferencesModal(
'Next Viewport',
'{shift}{rightarrow}'
@@ -350,9 +397,16 @@ describe('OHIF User Preferences', () => {
'Previous Viewport',
'{shift}{leftarrow}'
);
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Set 3 viewports layout
cy.setLayout(3, 1);
@@ -391,7 +445,7 @@ describe('OHIF User Preferences', () => {
it('checks error message when duplicated hotkeys are inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set duplicated hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{i}');
@@ -404,15 +458,11 @@ describe('OHIF User Preferences', () => {
.as('errorMsg')
.should('have.text', '"Invert" is already using the "i" shortcut.');
});
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks error message when invalid hotkey is inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set invalid hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{ctrl}Z');
@@ -425,16 +475,11 @@ describe('OHIF User Preferences', () => {
.as('errorMsg')
.should('have.text', '"ctrl+z" shortcut combination is not allowed');
});
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks error message when only modifier keys are inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set invalid modifier key: ctrl
cy.setNewHotkeyShortcutOnUserPreferencesModal('Zoom Out', '{ctrl}');
@@ -465,16 +510,11 @@ describe('OHIF User Preferences', () => {
'have.text',
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut"
);
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks if user can cancel changes made on User Preferences Hotkeys tab', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -482,10 +522,16 @@ describe('OHIF User Preferences', () => {
'{ctrl}{shift}S'
);
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
// Open User Preferences modal again
cy.openPreferences();
@@ -497,12 +543,11 @@ describe('OHIF User Preferences', () => {
.find('input')
.should('have.value', 'r');
});
cy.get('[data-cy="close-button"]').click();
});
it('checks if user can reset to default values on User Preferences Hotkeys tab', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -510,7 +555,7 @@ describe('OHIF User Preferences', () => {
'{ctrl}{shift}S'
);
//Save hotkeys
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
@@ -523,9 +568,6 @@ describe('OHIF User Preferences', () => {
.scrollIntoView()
.click();
// Open User Preferences modal again
cy.openPreferences();
//Check that hotkey for 'Rotate Right' function was not changed
cy.get('.HotkeysPreferences').within(() => {
cy.contains('Rotate Right') // label we're looking for
@@ -533,7 +575,183 @@ describe('OHIF User Preferences', () => {
.find('input')
.should('have.value', 'r');
});
});
});
context('W/L Preset Preferences', function() {
before(() => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
cy.initCommonElementsAliases();
// Open User Preferences modal
cy.openPreferences();
// Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Check table header
cy.get('.wlRow.header')
.should('contains.text', 'Preset')
.and('contains.text', 'Description')
.and('contains.text', 'Window')
.and('contains.text', 'Level');
//Check table has more than 1 row (more than header)
cy.get('.wlRow')
.its('length')
.should('be.greaterThan', 1);
});
// //TODO: Test blocked by issue #1551: https://github.com/OHIF/Viewers/issues/1551
// it('checks if user can add a new W/L preset', function() {
// let description = ':nth-child(8) > .description > .preferencesInput';
// let window = ':nth-child(8) > .window > .preferencesInput';
// let level = ':nth-child(8) > .level > .preferencesInput';
// let new_window_value = 150;
// let new_level_value = -600;
// // Check existing preset values
// cy.get(description).should('have.value', '');
// cy.get(window).should('have.value', '');
// cy.get(level).should('have.value', '');
// // Set new preset value
// cy.setWindowLevelPreset(
// 7,
// 'New Description',
// new_window_value,
// new_level_value
// );
// cy.get('@saveBtn').click();
// // Open User Preferences modal
// cy.openPreferences();
// // Navigate to Window Level tab
// cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// // Check recently added preset values
// cy.get(description).should('have.value', 'New Description');
// cy.get(window).should('have.value', new_window_value);
// cy.get(level).should('have.value', new_level_value);
// // Close User Preferences modal
// cy.get('[data-cy="close-button"]').click();
// // Check if new hotkey preset is working on viewport
// cy.get('body').type('8');
// cy.get('@viewportInfoBottomRight').should(
// 'contains.text',
// 'W: ' + new_window_value + ' L: ' + new_level_value
// );
// });
it('checks if user can remove an existing W/L preset', function() {
let description = ':nth-child(3) > .description > .preferencesInput';
let window = ':nth-child(3) > .window > .preferencesInput';
let level = ':nth-child(3) > .level > .preferencesInput';
// Check existing preset values
cy.get(description)
.should('not.have.value', '')
.clear();
cy.get(window)
.should('not.have.value', '')
.clear();
cy.get(level)
.should('not.have.value', '')
.clear();
// Save changes
cy.get('@saveBtn').click();
// Open User Preferences modal
cy.openPreferences();
// Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// Check recently added preset values
cy.get(description).should('have.value', '');
cy.get(window).should('have.value', '');
cy.get(level).should('have.value', '');
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
});
// //TODO: Test blocked by issue #1551: https://github.com/OHIF/Viewers/issues/1551
// it('checks if user can edit an existing W/L preset', function() {
// let description = ':nth-child(2) > .description > .preferencesInput';
// let window = ':nth-child(2) > .window > .preferencesInput';
// let level = ':nth-child(2) > .level > .preferencesInput';
// // Check existing preset values
// cy.get(description).should('have.value', 'Soft tissue');
// cy.get(window).should('have.value', '550');
// cy.get(level).should('have.value', '40');
// // Set new preset value
// cy.setWindowLevelPreset(1, 'Soft tissue New Description', 1220, 333);
// cy.get('@saveBtn').click();
// // Open User Preferences modal
// cy.openPreferences();
// // Navigate to Window Level tab
// cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// // Check recently added preset values
// cy.get(description).should('have.value', 'Soft tissue New Description');
// cy.get(window).should('have.value', '1220');
// cy.get(level).should('have.value', '333');
// });
it('checks if user can change the W/L by triggering different hotkeys with W/L presets', function() {
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
// Check if hotkey preset is working on viewport
cy.get('body').type('3');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 150 L: 90'
);
// Check if hotkey preset is working on viewport
cy.get('body').type('4');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 2500 L: 480'
);
});
it('checks if user can change the W/L by triggering different hotkeys with W/L presets on multiple viewports', function() {
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
// Set 3 viewports layout
cy.setLayout(3, 1);
cy.waitViewportImageLoading();
// Check if hotkey preset is working on viewport
cy.get('body').type('3');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 150 L: 90'
);
// Overlay information from 2nd viewport
let second_viewport_overlay =
'div:nth-child(2) > div > div.viewport-element > div.ViewportOverlay > div.bottom-right.overlay-element > div';
// Shift active viewport to Viewport #2
cy.get('body').type('{rightarrow}');
// Check if hotkey preset is working on viewport #2
cy.get('body').type('4');
cy.get(second_viewport_overlay).should('contains.text', 'W: 2500 L: 480');
// Set 1 viewport layout
cy.setLayout(1, 1);
});
});
});
@@ -4,7 +4,7 @@ describe('Visual Regression - OHIF PDF Extension', () => {
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
});v
});
it('drags and drop a PDF thumbnail into viewport', () => {
cy.get('[data-cy="thumbnail-list"]')
@@ -7,10 +7,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
beforeEach(() => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.get('@restoreBtn').scrollIntoView();
// Visual comparison
@@ -21,8 +22,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -59,12 +59,14 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.initCommonElementsAliases();
cy.resetViewport();
cy.resetUserHoktkeyPreferences();
cy.resetUserHotkeyPreferences();
// Open User Preferences modal
cy.openPreferences();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.get('@restoreBtn').scrollIntoView();
// Visual comparison
@@ -74,9 +76,18 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// Visual comparison
cy.percyCanvasSnapshot(
'User Preferences Modal - Window Level Presets Tab'
);
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Visual comparison
cy.percyCanvasSnapshot(
@@ -100,8 +111,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
});
it('checks if user can restore to default the language selection and application will be in English', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Set language to Spanish
cy.setLanguage('Spanish');
@@ -110,7 +120,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
cy.get('@restoreBtn')
.scrollIntoView()
@@ -136,8 +146,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
it('checks new hotkeys for "Next" and "Previous" Image on Viewport', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Next Image Viewport' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -84,6 +84,7 @@ export function initPreferencesModalAliases() {
cy.get('.OHIFModal').as('preferencesModal');
cy.get('[data-cy="hotkeys"]').as('userPreferencesHotkeysTab');
cy.get('[data-cy="general"]').as('userPreferencesGeneralTab');
cy.get('[data-cy="window-level"]').as('userPreferencesWindowLevelTab');
initPreferencesModalFooterBtnAliases();
}
+88 -18
View File
@@ -121,7 +121,7 @@ Cypress.Commands.add('waitVTKReformatting', () => {
Cypress.Commands.add('waitViewportImageLoading', () => {
// Wait for finish loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 20000 }).should($grid => {
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
expect($grid).not.to.contain.text('Load');
});
});
@@ -466,21 +466,54 @@ Cypress.Commands.add('openPreferences', () => {
});
});
Cypress.Commands.add('changePreferencesTab', tabAlias => {
Cypress.Commands.add('selectPreferencesTab', tabAlias => {
cy.initPreferencesModalAliases();
cy.get(tabAlias).click();
cy.get(tabAlias)
.click()
.should('have.class', 'active');
initPreferencesModalFooterBtnAliases();
});
Cypress.Commands.add('resetUserHoktkeyPreferences', () => {
Cypress.Commands.add('resetUserHotkeyPreferences', () => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
cy.selectPreferencesTab('@userPreferencesHotkeysTab').then(() => {
cy.log('Reset Hotkeys to Default Preferences');
cy.get('@restoreBtn').click();
});
cy.log('Reset to Default Preferences');
cy.get('@restoreBtn').click();
cy.get('@saveBtn').click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
// Click on Save Button
cy.get('@saveBtn').click();
});
});
Cypress.Commands.add('resetUserGeneralPreferences', () => {
// Open User Preferences modal
cy.openPreferences();
cy.selectPreferencesTab('@userPreferencesGeneralTab').then(() => {
cy.log('Reset Language to Default Preferences');
cy.get('@restoreBtn').click();
});
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
// Click on Save Button
cy.get('@saveBtn').click();
});
});
Cypress.Commands.add(
@@ -499,6 +532,37 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add(
'setWindowLevelPreset',
(preset_index, description_value, window_value, level_value) => {
let index = parseInt(preset_index) + 1;
// Set new Description value
cy.get(':nth-child(' + index + ') > .description > .preferencesInput')
.clear()
.type(description_value, {
force: true,
})
.blur();
// Set new Window value
cy.get(':nth-child(' + index + ') > .window > .preferencesInput')
.clear()
.type(window_value, {
force: true,
})
.blur();
// Set new Level value
cy.get(':nth-child(' + index + ') > .level > .preferencesInput')
.clear()
.type(level_value, {
force: true,
})
.blur();
}
);
Cypress.Commands.add('openDownloadImageModal', () => {
// Click on More button
cy.get('[data-cy="more"]')
@@ -513,12 +577,8 @@ Cypress.Commands.add('openDownloadImageModal', () => {
Cypress.Commands.add('setLanguage', (language, save = true) => {
cy.openPreferences();
cy.get('@userPreferencesGeneralTab')
.click()
.should('have.class', 'active');
initPreferencesModalFooterBtnAliases();
cy.initPreferencesModalAliases();
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -526,8 +586,18 @@ Cypress.Commands.add('setLanguage', (language, save = true) => {
// Select Language and Save/Cancel
cy.get('#language-select').select(language);
const toClick = save ? '@saveBtn' : '@cancelBtn';
cy.get(toClick)
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
//Click on Save/Cancel button
const toClick = save ? '@saveBtn' : '@cancelBtn';
cy.get(toClick)
.scrollIntoView()
.click();
});
});
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "3.8.8",
"version": "3.8.16",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -48,14 +48,14 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.6.4",
"@ohif/extension-cornerstone": "^2.5.0",
"@ohif/extension-dicom-html": "^1.1.1",
"@ohif/extension-dicom-microscopy": "^0.50.7",
"@ohif/extension-dicom-pdf": "^1.0.2",
"@ohif/extension-dicom-segmentation": "^0.1.0",
"@ohif/core": "^2.6.7",
"@ohif/extension-cornerstone": "^2.5.1",
"@ohif/extension-dicom-html": "^1.1.2",
"@ohif/extension-dicom-microscopy": "^0.50.9",
"@ohif/extension-dicom-pdf": "^1.0.3",
"@ohif/extension-dicom-segmentation": "^0.1.1",
"@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-vtk": "^1.3.5",
"@ohif/extension-vtk": "^1.3.8",
"@ohif/i18n": "^0.52.7",
"@ohif/ui": "^1.4.1",
"@tanem/react-nprogress": "^1.1.25",
+1 -5
View File
@@ -34,10 +34,7 @@ import i18n from '@ohif/i18n';
import './config';
/** Utils */
import {
getUserManagerForOpenIdConnectClient,
initWebWorkers,
} from './utils/index.js';
import { getUserManagerForOpenIdConnectClient } from './utils/index.js';
/** Extensions */
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
@@ -140,7 +137,6 @@ class App extends Component {
*/
_initHotkeys(appConfigHotkeys);
_initServers(servers);
initWebWorkers();
}
render() {
@@ -103,6 +103,8 @@ export default function jumpToRowItem(
displaySet.frameIndex = data.frameIndex;
}
viewportIndex =
(viewportIndex + viewportsState.activeViewportIndex) % numViewports;
viewportSpecificData.push({
viewportIndex,
displaySet,
@@ -11,7 +11,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) {
const ViewportGrid = function (props) {
const {
activeViewportIndex,
availablePlugins,
@@ -37,7 +37,7 @@ const ViewportGrid = function(props) {
viewportData.forEach(displaySet => {
loadAndCacheDerivedDisplaySets(displaySet, studies);
});
}, [viewportData]);
}, [studies, viewportData]);
const getViewportPanes = () =>
layout.viewports.map((layout, viewportIndex) => {
-30
View File
@@ -1,7 +1,4 @@
import OHIF from '@ohif/core';
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import dicomParser from 'dicom-parser';
import version from './version.js';
let homepage;
@@ -15,31 +12,4 @@ window.info = {
homepage,
};
// For debugging
//if (process.env.node_env === 'development') {
window.cornerstone = cornerstone;
window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader;
//}
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
OHIF.user.getAccessToken = () => {
// TODO: Get the Redux store from somewhere else
const state = window.store.getState();
if (!state.oidc || !state.oidc.user) {
return;
}
return state.oidc.user.access_token;
};
cornerstoneWADOImageLoader.configure({
beforeSend: function(xhr) {
const headers = OHIF.DICOMWeb.getAuthorizationHeader();
if (headers.Authorization) {
xhr.setRequestHeader('Authorization', headers.Authorization);
}
},
});
@@ -136,12 +136,32 @@ class ToolbarRow extends Component {
}
if (activeContextsChanged) {
this.setState({
toolbarButtons: _getVisibleToolbarButtons.call(this),
});
this.setState(
{
toolbarButtons: _getVisibleToolbarButtons.call(this),
},
this.closeCineDialogIfNotApplicable
);
}
}
closeCineDialogIfNotApplicable = () => {
const { dialog } = this.props;
let { dialogId, activeButtons, toolbarButtons } = this.state;
if (dialogId) {
const cineButtonPresent = toolbarButtons.find(
button => button.options && button.options.behavior === 'CINE'
);
if (!cineButtonPresent) {
dialog.dismiss({ id: dialogId });
activeButtons = activeButtons.filter(
button => button.options && button.options.behavior !== 'CINE'
);
this.setState({ dialogId: null, activeButtons });
}
}
};
render() {
const buttonComponents = _getButtonComponents.call(
this,
+1 -1
View File
@@ -3,7 +3,7 @@ import FileLoaderService from './localFileLoaders/fileLoaderService';
const processFile = async file => {
try {
const fileLoaderService = new FileLoaderService(file);
const imageId = fileLoaderService.addFile(file);
const imageId = await fileLoaderService.addFile(file);
const image = await fileLoaderService.loadFile(file, imageId);
const dataset = await fileLoaderService.getDataset(image, imageId);
const studies = await fileLoaderService.getStudies(dataset, imageId);
@@ -1,13 +1,14 @@
import * as dcmjs from 'dcmjs';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
import OHIF from '@ohif/core';
import { getCornerstoneWADOImageLoader } from '../../utils/cornerstoneWADOImageLoader';
const metadataProvider = OHIF.cornerstone.metadataProvider;
const DICOMFileLoader = new (class extends FileLoader {
fileType = 'application/dicom';
loadFile(file, imageId) {
async loadFile(file, imageId) {
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
@@ -1,7 +1,7 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
import PDFFileLoader from './pdfFileLoader';
import DICOMFileLoader from './dicomFileLoader';
import { getCornerstoneWADOImageLoader } from '../../utils/cornerstoneWADOImageLoader';
class FileLoaderService extends FileLoader {
fileType;
@@ -53,7 +53,8 @@ class FileLoaderService extends FileLoader {
return result;
}
addFile(file) {
async addFile(file) {
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
return cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
}
@@ -1,9 +1,10 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
import { getCornerstoneWADOImageLoader } from '../../utils/cornerstoneWADOImageLoader';
const PDFFileLoader = new (class extends FileLoader {
fileType = 'application/pdf';
loadFile(file, imageId) {
async loadFile(file, imageId) {
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
+16 -7
View File
@@ -1,4 +1,5 @@
import asyncComponent from '../components/AsyncComponent.js';
import { withCornerstone } from '../utils/cornerstoneWADOImageLoader';
import OHIF from '@ohif/core';
const { urlUtil: UrlUtil } = OHIF.utils;
@@ -9,8 +10,10 @@ const IHEInvokeImageDisplay = asyncComponent(() =>
/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js'
)
);
const ViewerRouting = asyncComponent(() =>
import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js')
const ViewerRouting = withCornerstone(
asyncComponent(() =>
import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js')
)
);
const StudyListRouting = asyncComponent(() =>
@@ -18,12 +21,18 @@ const StudyListRouting = asyncComponent(() =>
/* webpackChunkName: "StudyListRouting" */ '../studylist/StudyListRouting.js'
)
);
const StandaloneRouting = asyncComponent(() =>
import(/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js')
const StandaloneRouting = withCornerstone(
asyncComponent(() =>
import(
/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js'
)
)
);
const ViewerLocalFileData = asyncComponent(() =>
import(
/* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js'
const ViewerLocalFileData = withCornerstone(
asyncComponent(() =>
import(
/* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js'
)
)
);
@@ -0,0 +1,79 @@
import OHIF from '@ohif/core';
import cornerstone from 'cornerstone-core';
import dicomParser from 'dicom-parser';
import React from 'react';
export function getCornerstoneWADOImageLoader() {
return import(
/* webpackChunkName: "CornerstoneWADOImageLoader" */ 'cornerstone-wado-image-loader'
);
}
let initialized = false;
export async function initCornerstoneWADOImageLoader() {
if (initialized) return;
const cornerstoneWADOImageLoader = await getCornerstoneWADOImageLoader();
// For debugging
//if (process.env.node_env === 'development') {
window.cornerstoneWADOImageLoader = cornerstoneWADOImageLoader;
//}
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
OHIF.user.getAccessToken = () => {
// TODO: Get the Redux store from somewhere else
const state = window.store.getState();
if (!state.oidc || !state.oidc.user) {
return;
}
return state.oidc.user.accesstoken;
};
cornerstoneWADOImageLoader.configure({
beforeSend: function (xhr) {
const headers = OHIF.DICOMWeb.getAuthorizationHeader();
if (headers.Authorization) {
xhr.setRequestHeader('Authorization', headers.Authorization);
}
},
});
const config = {
maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
startWebWorkersOnDemand: true,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
usePDFJS: false,
strict: false,
},
},
};
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
initialized = true;
}
export function withCornerstone(Component) {
// eslint-disable-next-line react/display-name
return props => {
const [
cornerstoneWADOImageLoaderInitialized,
setCornerstoneWADOImageLoaderInitialized,
] = React.useState(false);
React.useEffect(() => {
initCornerstoneWADOImageLoader().then(() =>
setCornerstoneWADOImageLoaderInitialized(true)
);
}, []);
if (!cornerstoneWADOImageLoaderInitialized) {
return null;
}
return <Component {...props} />;
};
}
+1 -2
View File
@@ -1,4 +1,3 @@
import getUserManagerForOpenIdConnectClient from './getUserManagerForOpenIdConnectClient.js';
import initWebWorkers from './initWebWorkers.js';
export { getUserManagerForOpenIdConnectClient, initWebWorkers };
export { getUserManagerForOpenIdConnectClient };
+1 -1
View File
@@ -5,7 +5,7 @@ describe('utils', () => {
const utilExports = Object.keys(utils).sort();
expect(utilExports).toEqual(
['getUserManagerForOpenIdConnectClient', 'initWebWorkers'].sort()
['getUserManagerForOpenIdConnectClient'].sort()
);
});
});
@@ -1,22 +0,0 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
let initialized = false;
export default function initWebWorkers() {
const config = {
maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
startWebWorkersOnDemand: true,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
usePDFJS: false,
strict: false,
},
},
};
if (!initialized) {
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
initialized = true;
}
}
@@ -1,23 +0,0 @@
import initWebWorkers from './initWebWorkers.js';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
describe('initWebWorkers', () => {
it("initializes cornerstoneWADOImageLoader's web workers", () => {
initWebWorkers();
expect(
cornerstoneWADOImageLoader.webWorkerManager.initialize
).toHaveBeenCalled();
});
});
describe('initWebWorkers', () => {
it("initializes cornerstoneWADOImageLoader's web workers only once", () => {
initWebWorkers();
initWebWorkers();
expect(
cornerstoneWADOImageLoader.webWorkerManager.initialize
).toHaveBeenCalledTimes(1);
});
});