Compare commits

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

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

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

* Limit concurrent series metadata requests

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

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

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

* Address comments from code review

* Comment out misbehaving test

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

* Lazy initializing of CornerstoneWADIImageLoader - delayed till Viewer creation

* Async loading of react-vtkjs-viewport

* withCornerstone HOC to wrap routes requiring CornerstoneWADOImageLoader initiazation

* removed dead code in comments

* initWeWorkers is no more. Remove al its references.

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

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

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

* Fixed incorrect import from OHIFDicomPDFSopClassHandler to ConnectedOHIFDicomPDFViewer

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

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

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

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

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

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

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

* Rename to getPixelSpacingInformation
2020-03-24 18:10:58 +01:00
87 changed files with 1434 additions and 712 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();
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.8...@ohif/extension-vtk@1.3.9) (2020-04-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [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
## [1.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.3...@ohif/extension-vtk@1.3.4) (2020-03-24)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.2...@ohif/extension-vtk@1.3.3) (2020-03-24)
**Note:** Version bump only for package @ohif/extension-vtk
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.3.3",
"version": "1.3.9",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.8.3"
},
"devDependencies": {
"@ohif/core": "^2.6.2",
"@ohif/ui": "^1.4.1",
"@ohif/core": "^2.6.8",
"@ohif/ui": "^1.4.2",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3",
+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'
);
}
+61
View File
@@ -3,6 +3,67 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.6.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.7...@ohif/core@2.6.8) (2020-04-06)
**Note:** Version bump only for package @ohif/core
## [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)
### Bug Fixes
* Add support for single entries in SequenceOfUltrasoundRegions. M… ([#1559](https://github.com/OHIF/Viewers/issues/1559)) ([c1a0d3c](https://github.com/OHIF/Viewers/commit/c1a0d3c662d143b62dfbf1c01f6ce394af3756ca))
* disable autoFreeze of immer, even in dev mode ([#1560](https://github.com/OHIF/Viewers/issues/1560)) ([d604eba](https://github.com/OHIF/Viewers/commit/d604ebaffd93f688eadd0081e402f27074dd226b))
## [2.6.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.2...@ohif/core@2.6.3) (2020-03-24)
### Bug Fixes
* Ensure we take into account pixel spacing fields properly ([#1555](https://github.com/OHIF/Viewers/issues/1555)) ([77ab0ad](https://github.com/OHIF/Viewers/commit/77ab0ad9a14a135b5560741fc1600704df08c141))
## [2.6.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.1...@ohif/core@2.6.2) (2020-03-24)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.6.2",
"version": "2.6.8",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -39,7 +39,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "^0.8.3",
"dcmjs": "^0.12.0",
"dicomweb-client": "^0.5.2",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
@@ -10,6 +10,13 @@ const findMostRecentStructuredReport = studies => {
studies.forEach(study => {
const allSeries = study.getSeries ? study.getSeries() : [];
allSeries.forEach(series => {
// Skip series that may not have instances yet
// This can happen if we have retrieved just the initial
// details about the series via QIDO-RS, but not the full metadata
if (!series.instances.length) {
return;
}
if (isStructuredReportSeries(series)) {
if (
!mostRecentStructuredReport ||
+4 -17
View File
@@ -1,7 +1,7 @@
import * as dcmjs from 'dcmjs';
import queryString from 'query-string';
import dicomParser from 'dicom-parser';
import { getFallbackTagFromInstance } from '../utils/metadataProvider/metadataProviderFallbackTags';
import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
@@ -161,18 +161,6 @@ class MetadataProvider {
return instance[naturalizedTagOrWADOImageLoaderTag];
}
if (options.fallback) {
// Perhaps the tag has fallbacks?
const fallbackTag = getFallbackTagFromInstance(
naturalizedTagOrWADOImageLoaderTag,
instance
);
if (fallbackTag) {
return fallbackTag;
}
}
// Maybe its a legacy CornerstoneWADOImageLoader tag then:
return this._getCornerstoneWADOImageLoaderTag(
naturalizedTagOrWADOImageLoaderTag,
@@ -218,10 +206,9 @@ class MetadataProvider {
const { ImageOrientationPatient } = instance;
// Fallback for DX images.
const PixelSpacing = getFallbackTagFromInstance(
'PixelSpacing',
instance
);
// TODO: We should use the rest of the results of this function
// to update the UI somehow
const { PixelSpacing } = getPixelSpacingInformation(instance);
let rowPixelSpacing;
let columnPixelSpacing;
@@ -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
);
@@ -100,20 +100,32 @@ export class StudyMetadata extends Metadata {
* Split a series metadata object into display sets
* @param {Array} sopClassHandlerModules List of SOP Class Modules
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
* @param {Array} [givenDisplaySets] An optional list to which the display sets will be appended
* @returns {Array} The list of display sets created for the given series object
*/
_createDisplaySetsForSeries(
sopClassHandlerModules,
series,
givenDisplaySets
) {
const study = this;
const displaySets = Array.isArray(givenDisplaySets) ? givenDisplaySets : [];
const displaySets = [];
const anyInstances = series.getInstanceCount() > 0;
if (!anyInstances) {
return;
const displaySet = new ImageSet([]);
const seriesData = series.getData();
displaySet.setAttributes({
displaySetInstanceUID: displaySet.uid,
SeriesInstanceUID: seriesData.SeriesInstanceUID,
SeriesDescription: seriesData.SeriesDescription,
SeriesNumber: seriesData.SeriesNumber,
Modality: seriesData.Modality,
});
displaySets.push(displaySet);
return displaySets;
}
const sopClassUIDs = getSopClassUIDs(series);
@@ -208,20 +220,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
);
@@ -281,13 +294,14 @@ export class StudyMetadata extends Metadata {
// Loop through the series (SeriesMetadata)
this.forEachSeries(
series =>
void this._createDisplaySetsForSeries(
sopClassHandlerModules,
series,
displaySets
)
);
series => {
const displaySetsForSeries = this._createDisplaySetsForSeries(
sopClassHandlerModules,
series,
);
displaySets.push(...displaySetsForSeries);
});
return sortDisplaySetList(displaySets);
}
@@ -303,13 +317,27 @@ export class StudyMetadata extends Metadata {
* @returns {boolean} Returns true on success or false on failure (e.g., the series does not belong to this study)
*/
createAndAddDisplaySetsForSeries(sopClassHandlerModules, series) {
if (this.containsSeries(series)) {
this.setDisplaySets(
this._createDisplaySetsForSeries(sopClassHandlerModules, series)
);
return true;
if (!this.containsSeries(series)) {
return false;
}
return false;
const displaySets = this._createDisplaySetsForSeries(sopClassHandlerModules, series)
// Note: filtering in place because this._displaySets has writable: false
for (let i = this._displaySets.length - 1; i >= 0; i--) {
const displaySet = this._displaySets[i];
if (displaySet.SeriesInstanceUID === series.getSeriesInstanceUID()) {
this._displaySets.splice(i, 1);
}
}
displaySets.forEach(displaySet => {
this.addDisplaySet(displaySet);
});
this.sortDisplaySets();
return true;
}
/**
@@ -318,6 +346,9 @@ export class StudyMetadata extends Metadata {
*/
setDisplaySets(displaySets) {
if (Array.isArray(displaySets) && displaySets.length > 0) {
// TODO: This is weird, can we just switch it to writable: true?
this._displaySets.splice(0);
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
this.sortDisplaySets();
}
@@ -406,6 +437,30 @@ export class StudyMetadata extends Metadata {
return result;
}
/**
* Update a series in the current study by SeriesInstanceUID.
* @param {String} SeriesInstanceUID The SeriesInstanceUID to be updated
* @param {SeriesMetadata} series The series to be added to the current study.
* @returns {boolean} Returns true on success, false otherwise.
*/
updateSeries(SeriesInstanceUID, series) {
const index = this._series.findIndex(series => {
return series.getSeriesInstanceUID() === SeriesInstanceUID;
});
if (index < 0) {
return false;
}
if (!(series instanceof SeriesMetadata)) {
throw new Error('Series must be an instance of SeriesMetadata');
}
this._series[index] = series;
return true;
}
/**
* Find a series by index.
* @param {number} index An integer representing a list index.
@@ -1,5 +1,5 @@
import cloneDeep from 'lodash.clonedeep';
import produce from 'immer';
import produce, { setAutoFreeze } from 'immer';
import {
CLEAR_VIEWPORT,
@@ -11,6 +11,8 @@ import {
SET_VIEWPORT_LAYOUT_AND_DATA,
} from './../constants/ActionTypes.js';
setAutoFreeze(false);
export const DEFAULT_STATE = {
numRows: 1,
numColumns: 1,
@@ -1,4 +1,5 @@
import { api } from 'dicomweb-client';
import dcmjs from 'dcmjs';
import DICOMWeb from '../../../DICOMWeb/';
import RetrieveMetadataLoader from './retrieveMetadataLoader';
import { sortStudySeries, sortingCriteria } from '../../sortStudy';
@@ -8,6 +9,8 @@ import {
addInstancesToStudy,
} from './studyInstanceHelpers';
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
/**
* Map series to an array of SeriesInstanceUID
* @param {Arrays} series list of Series Instance UIDs
@@ -101,15 +104,21 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
async preLoad() {
const preLoaders = this.getPreLoaders();
const result = await this.runLoaders(preLoaders);
// seriesData is the result of the QIDO-RS Search For Series request
// It's an array of Objects containing DICOM Tag values at the Series level
const seriesData = await this.runLoaders(preLoaders);
const seriesSorted = sortStudySeries(
result,
seriesData,
sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria
);
const seriesInstanceUIDsMap = mapStudySeries(seriesSorted);
return seriesInstanceUIDsMap;
return {
seriesInstanceUIDsMap,
seriesData
};
}
async load(preLoadData) {
@@ -118,7 +127,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
const seriesAsyncLoader = makeSeriesAsyncLoader(
client,
studyInstanceUID,
preLoadData
preLoadData.seriesInstanceUIDsMap
);
const firstSeries = await seriesAsyncLoader.next();
@@ -126,16 +135,38 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
return {
sopInstances: firstSeries.sopInstances,
asyncLoader: seriesAsyncLoader,
seriesData: preLoadData.seriesData,
};
}
async posLoad(loadData) {
const { server } = this;
const { sopInstances, asyncLoader } = loadData;
const { sopInstances, asyncLoader, seriesData } = loadData;
const study = await createStudyFromSOPInstanceList(server, sopInstances);
// TODO: Should this be in a helper
const seriesDataNaturalized = seriesData.map(naturalizeDataset);
seriesDataNaturalized.forEach((series, idx) => {
const seriesDataFromQIDO = {
SeriesInstanceUID: series.SeriesInstanceUID,
SeriesDescription: series.SeriesDescription,
SeriesNumber: series.SeriesNumber,
Modality: series.Modality,
instances: []
};
if (study.series[idx]) {
study.series[idx] = Object.assign(seriesDataFromQIDO, study.series[idx]);
} else {
study.series[idx] = seriesDataFromQIDO;
}
study.seriesMap[series.SeriesInstanceUID] = study.series[idx];
});
if (asyncLoader.hasNext()) {
attachSeriesLoader(server, study, asyncLoader);
}
@@ -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;
}
@@ -0,0 +1,130 @@
import log from '../../log';
export default function getPixelSpacingInformation(instance) {
// See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing
// TODO: Add Ultrasound region spacing
// TODO: Add manual calibration
// TODO: Use ENUMS from dcmjs
const projectionRadiographSOPClassUIDs = [
'1.2.840.10008.5.1.4.1.1.1', // CR Image Storage
'1.2.840.10008.5.1.4.1.1.1.1', // Digital X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.1.1', // Digital X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.1.2', // Digital Mammography X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.2.1', // Digital Mammography X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.1.3', // Digital Intra – oral X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.3.1', // Digital Intra – oral X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.12.1', // X-Ray Angiographic Image Storage
'1.2.840.10008.5.1.4.1.1.12.1.1', // Enhanced XA Image Storage
'1.2.840.10008.5.1.4.1.1.12.2', // X-Ray Radiofluoroscopic Image Storage
'1.2.840.10008.5.1.4.1.1.12.2.1', // Enhanced XRF Image Storage
'1.2.840.10008.5.1.4.1.1.12.3', // X-Ray Angiographic Bi-plane Image Storage Retired
];
const {
PixelSpacing,
ImagerPixelSpacing,
SOPClassUID,
PixelSpacingCalibrationType,
PixelSpacingCalibrationDescription,
EstimatedRadiographicMagnificationFactor,
SequenceOfUltrasoundRegions,
} = instance;
const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);
const TYPES = {
NOT_APPLICABLE: 'NOT_APPLICABLE',
UNKNOWN: 'UNKNOWN',
CALIBRATED: 'CALIBRATED',
DETECTOR: 'DETECTOR',
};
if (isProjection && !ImagerPixelSpacing) {
// If only Pixel Spacing is present, and this is a projection radiograph,
// PixelSpacing should be used, but the user should be informed that
// what it means is unknown
return {
PixelSpacing,
type: TYPES.UNKNOWN,
isProjection,
};
} else if (
PixelSpacing &&
ImagerPixelSpacing &&
PixelSpacing === ImagerPixelSpacing
) {
// If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,
// then the user should be informed that the measurements are at the detector plane
return {
PixelSpacing,
type: TYPES.DETECTOR,
isProjection,
};
} else if (
PixelSpacing &&
ImagerPixelSpacing &&
PixelSpacing !== ImagerPixelSpacing
) {
// If Imager Pixel Spacing and Pixel Spacing are present and they have different values,
// then the user should be informed that these are "calibrated"
// (in some unknown manner if Pixel Spacing Calibration Type and/or
// Pixel Spacing Calibration Description are absent)
return {
PixelSpacing,
type: TYPES.CALIBRATED,
isProjection,
PixelSpacingCalibrationType,
PixelSpacingCalibrationDescription,
};
} else if (!PixelSpacing && ImagerPixelSpacing) {
let CorrectedImagerPixelSpacing = ImagerPixelSpacing;
if (EstimatedRadiographicMagnificationFactor) {
// Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by
// Estimated Radiographic Magnification Factor and the user informed of that.
// TODO: should this correction be done before all of this logic?
CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(
pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor
);
} else {
log.info(
'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.'
);
}
return {
PixelSpacing: CorrectedImagerPixelSpacing,
isProjection,
};
} else if (
SequenceOfUltrasoundRegions &&
typeof SequenceOfUltrasoundRegions === 'object'
) {
const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;
const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];
return {
PixelSpacing: USPixelSpacing,
};
} else if (
SequenceOfUltrasoundRegions &&
Array.isArray(SequenceOfUltrasoundRegions) &&
SequenceOfUltrasoundRegions.length > 1
) {
log.warn(
'Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels.'
);
} else if (isProjection === false && !ImagerPixelSpacing) {
// If only Pixel Spacing is present, and this is not a projection radiograph,
// we can stop here
return {
PixelSpacing,
type: TYPES.NOT_APPLICABLE,
isProjection,
};
}
log.info(
'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'
);
}
@@ -1,27 +0,0 @@
import log from '../../log';
function getFallbackTagFromInstance(tag, instance) {
if (instance[tag]) {
return instance[tag];
}
const fallbackTags = fallbackTagsMap[tag];
if (fallbackTags) {
for (let i = 0; i < fallbackTags.length; i++) {
const fallbackTag = fallbackTags[i];
if (instance[fallbackTag]) {
log.info(`metadata provider fallback tag ${tag} to ${fallbackTag}`);
return instance[fallbackTag];
}
}
}
}
const fallbackTagsMap = {
PixelSpacing: ['ImagerPixelSpacing'],
};
export { fallbackTagsMap, getFallbackTagFromInstance };
+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.4.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.1...@ohif/ui@1.4.2) (2020-04-06)
**Note:** Version bump only for package @ohif/ui
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.0...@ohif/ui@1.4.1) (2020-03-17)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.4.1",
"version": "1.4.2",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
export function ViewportLoadingIndicator(props) {
return (
<div
className="loadingIndicator"
className="loading-indicator"
style={{
position: 'absolute',
top: 0,
+97
View File
@@ -3,6 +3,103 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.8.17](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.16...@ohif/viewer@3.8.17) (2020-04-06)
**Note:** Version bump only for package @ohif/viewer
## [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
## [3.8.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.6...@ohif/viewer@3.8.7) (2020-03-24)
**Note:** Version bump only for package @ohif/viewer
## [3.8.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.5...@ohif/viewer@3.8.6) (2020-03-24)
**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);
});
});
});
@@ -110,7 +110,7 @@ describe('OHIF Save Measurements', function() {
cy.get('@measurementsBtn').click();
});
it('checks if measurements of unsupported tools were not saved', function() {
/*it('checks if measurements of unsupported tools were not saved', function() {
// Add measurement for supported tool in the viewport
cy.addLengthMeasurement();
// Add measurement for unsupported tool in the viewport
@@ -146,5 +146,5 @@ describe('OHIF Save Measurements', function() {
// Close Measurements panel
cy.get('@measurementsBtn').click();
});
});*/
});
@@ -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();
});
});
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "3.8.6",
"version": "3.8.17",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -48,16 +48,16 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.6.2",
"@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.8",
"@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.3",
"@ohif/extension-vtk": "^1.3.9",
"@ohif/i18n": "^0.52.7",
"@ohif/ui": "^1.4.1",
"@ohif/ui": "^1.4.2",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
@@ -65,7 +65,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.10.1",
"dcmjs": "^0.12.0",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8",
+8
View File
@@ -117,4 +117,12 @@ window.config = {
},
],
cornerstoneExtensionConfig: {},
// Following property limits number of simultaneous series metadata requests.
// For http/1.x-only servers, set this to 5 or less to improve
// on first meaningful display in viewer
// If the server is particularly slow to respond to series metadata
// requests as it extracts the metadata from raw files everytime,
// try setting this to even lower value
// Leave it undefined for no limit, sutiable for HTTP/2 enabled servers
// maxConcurrentMetadataRequests: 5,
};
+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,
@@ -116,11 +116,16 @@ const _addSeriesToStudy = (studyMetadata, series) => {
extensionManager.modules['sopClassHandlerModule'];
const study = studyMetadata.getData();
const seriesMetadata = new OHIFSeriesMetadata(series, study);
studyMetadata.addSeries(seriesMetadata);
const existingSeries = studyMetadata.getSeriesByUID(series.SeriesInstanceUID);
if (existingSeries) {
studyMetadata.updateSeries(series.SeriesInstanceUID, seriesMetadata);
} else {
studyMetadata.addSeries(seriesMetadata);
}
studyMetadata.createAndAddDisplaySetsForSeries(
sopClassHandlerModules,
seriesMetadata,
false
);
study.displaySets = studyMetadata.getDisplaySets();
_updateStudyMetadataManager(study, studyMetadata);
@@ -148,25 +153,6 @@ const _updateStudyDisplaySets = (study, studyMetadata) => {
const _sortStudyDisplaySet = (study, studyMetadata) => {
studyMetadata.sortDisplaySets(study.displaySets);
};
const _loadRemainingSeries = studyMetadata => {
const { seriesLoader } = studyMetadata.getData();
if (!seriesLoader) {
return Promise.resolve();
}
const promisesLoaders = [];
while (seriesLoader.hasNext()) {
promisesLoaders.push(
seriesLoader
.next()
.then(
series => void _addSeriesToStudy(studyMetadata, series),
error => void log.error(error)
)
);
}
return Promise.all(promisesLoaders);
};
function ViewerRetrieveStudyData({
server,
@@ -180,7 +166,10 @@ function ViewerRetrieveStudyData({
const [isStudyLoaded, setIsStudyLoaded] = useState(false);
const snackbarContext = useSnackbarContext();
const { appConfig = {} } = useContext(AppContext);
const { filterQueryParam: isFilterStrategy = false } = appConfig;
const {
filterQueryParam: isFilterStrategy = false,
maxConcurrentMetadataRequests,
} = appConfig;
let cancelableSeriesPromises;
let cancelableStudiesPromises;
@@ -241,7 +230,7 @@ function ViewerRetrieveStudyData({
// Attempt to load remaning series if any
cancelableSeriesPromises[study.StudyInstanceUID] = makeCancelable(
_loadRemainingSeries(studyMetadata)
loadRemainingSeries(studyMetadata)
)
.then(result => {
if (result && !result.isCanceled) {
@@ -262,6 +251,27 @@ function ViewerRetrieveStudyData({
}
};
const forceRerender = () => setStudies(studies => [...studies]);
const loadRemainingSeries = async studyMetadata => {
const { seriesLoader } = studyMetadata.getData();
if (!seriesLoader) return;
const loadNextSeries = async () => {
if (!seriesLoader.hasNext()) return;
const series = await seriesLoader.next();
_addSeriesToStudy(studyMetadata, series);
forceRerender();
return loadNextSeries();
};
const concurrentRequestsAllowed = maxConcurrentMetadataRequests || studyMetadata.getSeriesCount();
const promises = Array(concurrentRequestsAllowed)
.fill(null)
.map(loadNextSeries);
await Promise.all(promises);
};
const loadStudies = async () => {
try {
const filters = {};
+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);
});
});
+9 -19
View File
@@ -918,7 +918,7 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/polyfill@^7.6.0":
"@babel/polyfill@^7.8.3":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.7.tgz#151ec24c7135481336168c3bd8b8bf0cf91c032f"
integrity sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w==
@@ -6385,25 +6385,15 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dcmjs@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.10.1.tgz#0655f8c0f7ed1357145f416e14efd4a45e2c74be"
integrity sha512-aoXkZXTp3Kh20C6I2i2AJjcHRRxqjmt7KWvJElRv2b93AfUPVplg6+/B2ioiJFSSESt7/Q9yjHRM8k0U1uRCag==
dcmjs@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.12.0.tgz#8b1634f9b66e452075295f5d1f2c5bf3dabdf03e"
integrity sha512-AZAnFMvzAxUv5+KWoZcxnTYflLKk0rNPeeFt2KrVAgaExFBFfDgJQh1lEgqdNlIP+XQDFrEEUmp726SQhgmVCg==
dependencies:
"@babel/polyfill" "^7.6.0"
"@babel/runtime" "^7.6.3"
"@babel/polyfill" "^7.8.3"
"@babel/runtime" "^7.8.4"
loglevelnext "^3.0.1"
ndarray "^1.0.18"
dcmjs@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.8.3.tgz#fff1b030b6cb2d6e2afb1aa99840bfa853724c31"
integrity sha512-eXQjqgtJf9+oseraKDNDm2A5F3Th4B2GJeZtjStj0IFXxjlbPOzdq3PfCyxdwfRaKOBIwr0q3YK/Vfs2CpQY8Q==
dependencies:
"@babel/polyfill" "^7.6.0"
"@babel/runtime" "^7.6.3"
loglevelnext "^3.0.1"
ndarray "^1.0.18"
ndarray "^1.0.19"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.1, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
@@ -13006,7 +12996,7 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
ndarray@^1.0.18:
ndarray@^1.0.19:
version "1.0.19"
resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e"
integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==