Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5175bf3e6 | ||
|
|
8c8f69accb | ||
|
|
f9037f831d | ||
|
|
9e0bd52c6a | ||
|
|
f1b017dc08 | ||
|
|
81dcbe294d | ||
|
|
e09a3d2e93 | ||
|
|
978a86bf12 | ||
|
|
0139a32213 | ||
|
|
d57158b59d | ||
|
|
4a7ce1c093 | ||
|
|
333677c40e | ||
|
|
b459b894bd | ||
|
|
c5a4860209 | ||
|
|
4f79bda7c9 | ||
|
|
7d22bb7d5a | ||
|
|
36822cc6ce | ||
|
|
e676f0b5b5 | ||
|
|
57de66a65f | ||
|
|
99022f2bac | ||
|
|
47b1e2837f | ||
|
|
d604ebaffd | ||
|
|
c1a0d3c662 | ||
|
|
16866a47a0 | ||
|
|
77ab0ad9a1 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
@@ -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 -->
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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 -->
|
||||
@@ -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 -->
|
||||
@@ -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,8 +2,8 @@
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Example](#example)
|
||||
- [Tips & Tricks](#/)
|
||||
- [Maintained Services](#/)
|
||||
- [Tips & Tricks](#tips--tricks)
|
||||
- [Maintained Services](#maintained-services)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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,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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
@@ -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,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,
|
||||
|
||||
+81
-84
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});*/
|
||||
});
|
||||
+1
-1
@@ -4,7 +4,7 @@ describe('Visual Regression - OHIF PDF Extension', () => {
|
||||
'1.2.826.0.13854362241694438965858641723883466450351448'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
});v
|
||||
});
|
||||
|
||||
it('drags and drop a PDF thumbnail into viewport', () => {
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
|
||||
+20
-11
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
import getUserManagerForOpenIdConnectClient from './getUserManagerForOpenIdConnectClient.js';
|
||||
import initWebWorkers from './initWebWorkers.js';
|
||||
|
||||
export { getUserManagerForOpenIdConnectClient, initWebWorkers };
|
||||
export { getUserManagerForOpenIdConnectClient };
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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==
|
||||
|
||||
Reference in new issue
Block a user