Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1953118c8a | ||
|
|
7807f849f2 | ||
|
|
b426256df5 | ||
|
|
c9b7e97f9c | ||
|
|
0fc7d93d73 | ||
|
|
33307d3cd2 | ||
|
|
c3ce909434 | ||
|
|
ad2c750503 | ||
|
|
cceb61f123 | ||
|
|
13bcba8c39 | ||
|
|
d2db026158 | ||
|
|
e4b0552db4 | ||
|
|
d7ba2fac69 | ||
|
|
761d54fbbf | ||
|
|
79548d2380 | ||
|
|
6a401c9e52 | ||
|
|
23376d279d | ||
|
|
4110b0422c | ||
|
|
ed501bb462 | ||
|
|
abf050e162 | ||
|
|
35ed82e161 | ||
|
|
d0ccc22fd9 | ||
|
|
31841c495f | ||
|
|
65505d9bce | ||
|
|
460fdeb534 | ||
|
|
f4b7091ae4 | ||
|
|
1e72684455 | ||
|
|
5b0b6301b9 | ||
|
|
03a34a75a1 | ||
|
|
729272a985 | ||
|
|
9194ec2404 | ||
|
|
52c95a1209 | ||
|
|
8903adaea7 | ||
|
|
9481e2a99e | ||
|
|
f783fd7650 | ||
|
|
79afee7615 | ||
|
|
539141fe5a | ||
|
|
a046c6c901 | ||
|
|
073be065cf | ||
|
|
252536a1e6 | ||
|
|
53b1d71d56 | ||
|
|
702d99cb93 | ||
|
|
bb7b78bf33 | ||
|
|
b796fc9c62 | ||
|
|
66c587b139 | ||
|
|
f6e2e58aad | ||
|
|
489d4b0584 | ||
|
|
36019156da | ||
|
|
f2a371fc2d | ||
|
|
ad2f038817 | ||
|
|
8f2604509d | ||
|
|
ea0963d75f | ||
|
|
46d5718011 | ||
|
|
f8740e04f3 | ||
|
|
f6a0db753a | ||
|
|
8518e179ee | ||
|
|
3f40cdced7 |
No files matched your search
+11
-7
@@ -25,11 +25,9 @@ executors:
|
||||
docker:
|
||||
# Primary container image where all steps run.
|
||||
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
|
||||
- image: 'ohif/viewer-testdata:0.1-test'
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:14.15.0
|
||||
- image: cimg/node:14.18
|
||||
environment:
|
||||
TERM: xterm # Enable colors in term
|
||||
QUICK_BUILD: true
|
||||
@@ -119,6 +117,7 @@ jobs:
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
- setup_remote_docker:
|
||||
version: 19.03.13
|
||||
docker_layer_caching: false
|
||||
|
||||
- run:
|
||||
@@ -295,6 +294,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
version: 19.03.13
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Build and push Docker image
|
||||
@@ -347,7 +347,8 @@ workflows:
|
||||
store_artifacts: true
|
||||
working_directory: platform/viewer
|
||||
build:
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
|
||||
git submodule update --init &&
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
|
||||
yarn run build
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
||||
@@ -375,7 +376,8 @@ workflows:
|
||||
store_artifacts: true
|
||||
working_directory: platform/viewer
|
||||
build:
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
|
||||
git submodule update --init &&
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
|
||||
yarn run build:package
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
||||
@@ -408,7 +410,8 @@ workflows:
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build:
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
|
||||
git submodule update --init &&
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
|
||||
yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
@@ -487,7 +490,8 @@ workflows:
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build:
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js
|
||||
git submodule update --init &&
|
||||
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js
|
||||
yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
custom: https://giving.massgeneral.org/ohif
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: "\U0001F917 Support Question"
|
||||
about: "I have a question \U0001F4AC"
|
||||
title: ''
|
||||
labels: 'Community: Question :question:, Triage :white_flag:'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> :hand: We are a small team with limited resources. Your question is much more
|
||||
> likely to be answered if it is
|
||||
> [a good question](https://stackoverflow.com/help/how-to-ask)
|
||||
|
||||
**Description**
|
||||
|
||||
Questions can often be answered by our documentation. Unable to find an answer
|
||||
in our docs? We'll try to help. In the meantime, if you answer your own
|
||||
question, please respond with the answer here so that others may benefit as
|
||||
well. Better yet, open a PR to expand our docs ^\_^
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤗 Support Question
|
||||
url: https://community.ohif.org/
|
||||
about: Please use our forum if you have questions or need help.
|
||||
@@ -10,6 +10,9 @@ junit.xml
|
||||
coverage/
|
||||
.docz/
|
||||
*.js.map
|
||||
.yarn
|
||||
platform/docs/.docusaurus
|
||||
platform/ui/storybook-static
|
||||
|
||||
# YALC (for Erik)
|
||||
.yalc
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "testdata"]
|
||||
path = testdata
|
||||
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
|
||||
@@ -0,0 +1 @@
|
||||
14.18.0
|
||||
@@ -28,7 +28,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
|
||||
const config = {
|
||||
mode: isProdBuild ? 'production' : 'development',
|
||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map',
|
||||
devtool: isProdBuild ? 'source-map' : 'eval-cheap-module-source-map',
|
||||
entry: {
|
||||
app: `${SRC_DIR}/index.js`,
|
||||
},
|
||||
|
||||
@@ -273,6 +273,17 @@ development of OHIF Viewer:
|
||||
Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
|
||||
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
|
||||
version of the OHIF Viewer.
|
||||
|
||||
### Research notice
|
||||
Please note that this repository is participating in a study into sustainability
|
||||
of open source projects. Data will be gathered about this repository for
|
||||
approximately the next 12 months, starting from June 2021.
|
||||
|
||||
Data collected will include number of contributors, number of PRs, time taken to
|
||||
close/merge these PRs, and issues closed.
|
||||
|
||||
For more information, please visit
|
||||
[our informational page](https://sustainable-open-science-and-software.github.io/) or download our [participant information sheet](https://sustainable-open-science-and-software.github.io/assets/PIS_sustainable_software.pdf).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ OHIF Viewer and its medical imaging data. This post covers one of many potential
|
||||
setups that accomplish that. Please note, noticably absent is user account
|
||||
control.
|
||||
|
||||
Do not use this recipe to host sensitive medical data on the open web. Depending
|
||||
> **Do not use this recipe to host sensitive medical data on the open web!**
|
||||
|
||||
Depending
|
||||
on your company's policies, this may be an appropriate setup on an internal
|
||||
network when protected with a server's basic authentication. For a more robust
|
||||
setup, check out our [user account control recipe](./user-account-control.md)
|
||||
@@ -23,13 +25,13 @@ Our two biggest hurdles when hosting our image archive and web client are:
|
||||
|
||||
### Handling Web Requests
|
||||
|
||||
We mittigate our first issue by allowing [Nginx][nginx] to handle incoming web
|
||||
We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web
|
||||
requests. Nginx is open source software for web serving, reverse proxying,
|
||||
caching, and more. It's designed for maximum performance and stability --
|
||||
allowing us to more reliably serve content than Orthanc's built-in server can.
|
||||
|
||||
More specifically, we accomplish this by using a
|
||||
[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve
|
||||
[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve
|
||||
resources from our image archive (Orthanc), and when accessing its web admin.
|
||||
|
||||
> A reverse proxy is a type of proxy server that retrieves resources on behalf
|
||||
@@ -38,7 +40,7 @@ resources from our image archive (Orthanc), and when accessing its web admin.
|
||||
|
||||
### CORS Issues
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to
|
||||
[Cross-Origin Resource Sharing][understanding-cors] (CORS) is a mechanism that uses HTTP headers to
|
||||
tell a browser which web applications have permission to access selected
|
||||
resources from a server at a different origin (domain, protocol, port). IE. By
|
||||
default, a Web App located at `http://my-website.com` can't access resources
|
||||
@@ -51,9 +53,6 @@ We can solve this one of two ways:
|
||||
|
||||
**This solution uses the first approach.**
|
||||
|
||||
You can read more about CORS in this Medium article: [Understanding
|
||||
CORS][understanding-cors]
|
||||
|
||||
### Diagram
|
||||
|
||||
This setup allows us to create a setup similar to the one pictured below:
|
||||
@@ -73,6 +72,8 @@ This setup allows us to create a setup similar to the one pictured below:
|
||||
- Docker
|
||||
- [Docker for Mac](https://docs.docker.com/docker-for-mac/)
|
||||
- [Docker for Windows](https://docs.docker.com/docker-for-windows/)
|
||||
- Node 16.x with optional Python and Visual Studio Build Tools ([webpack 4 has an issue](https://github.com/webpack/webpack/issues/14532) with Node 17.x)
|
||||
- [Node.js](https://nodejs.org/en/download/)
|
||||
|
||||
_Not sure if you have `docker` installed already? Try running `docker --version`
|
||||
in command prompt or terminal_
|
||||
@@ -81,7 +82,7 @@ in command prompt or terminal_
|
||||
|
||||
_Spin Things Up_
|
||||
|
||||
- Navigate to `<project-root>/docker/OpenResty-Orthanc` in your shell
|
||||
- Navigate to `./platform/viewer/.recipes/OpenResty-Orthanc` in your shell
|
||||
- Run `docker-compose up`
|
||||
|
||||
_Upload Your First Study_
|
||||
@@ -94,16 +95,16 @@ _Upload Your First Study_
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
_Exit code 137_
|
||||
#### Exit code 137
|
||||
|
||||
This means Docker ran out of memory. Open Docker Desktop, go to the `advanced`
|
||||
tab, and increase the amount of Memory available.
|
||||
|
||||
_Cannot create container for service X_
|
||||
#### Cannot create container for service X
|
||||
|
||||
Use this one with caution: `docker system prune`
|
||||
|
||||
_X is already running_
|
||||
#### X is already running
|
||||
|
||||
Stop running all containers:
|
||||
|
||||
@@ -120,15 +121,12 @@ 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
|
||||
configuration we use is set to [a specific file][config-ohif] 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`
|
||||
|
||||
You can find the configuration we're using here:
|
||||
`/public/config/docker_openresty-orthanc.js`
|
||||
|
||||
To rebuild the `webapp` image created by our `dockerfile` after updating the
|
||||
Viewer's configuration, you can run:
|
||||
|
||||
@@ -137,7 +135,7 @@ Viewer's configuration, you can run:
|
||||
|
||||
#### Other
|
||||
|
||||
All other files are found in: `/docker/OpenResty-Orthanc/`
|
||||
All other files in this directory include
|
||||
|
||||
| Service | Configuration | Docs |
|
||||
| ----------------- | --------------------------------- | ------------------------------------------- |
|
||||
@@ -236,6 +234,8 @@ 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-ohif]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/docker_openresty-orthanc.js
|
||||
[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
|
||||
|
||||
@@ -67,6 +67,33 @@ window.config = {
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
/**
|
||||
* Disable caching of servers configuration.
|
||||
*
|
||||
* There will be no effect if you update the servers property of this config
|
||||
* while your application is running because this property is cached in local storage.
|
||||
*/
|
||||
disableServersCache: false,
|
||||
/**
|
||||
* OHIF's study prefetcher configuration.
|
||||
*
|
||||
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
|
||||
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
|
||||
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
|
||||
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
|
||||
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
|
||||
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
|
||||
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
|
||||
*/
|
||||
studyPrefetcher: {
|
||||
enabled: true,
|
||||
order: 'all',
|
||||
displaySetCount: 1,
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
displayProgress: false,
|
||||
includeActiveDisplaySet: true,
|
||||
},
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
@@ -81,7 +108,14 @@ window.config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
/**
|
||||
* Hotkey definitions.
|
||||
* Supported Keys: https://craig.is/killing/mice
|
||||
*
|
||||
* @param {string} commandName
|
||||
* @param {string} label
|
||||
* @param {array} keys
|
||||
*/
|
||||
hotkeys: [
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
@@ -97,14 +131,16 @@ window.config = {
|
||||
keys: ['v'],
|
||||
},
|
||||
],
|
||||
/* Configuration passed to the bundled cornerstone extension
|
||||
/**
|
||||
* Configuration passed to the bundled cornerstone extension
|
||||
*
|
||||
* The cornerstone extension is currently tightly coupled to the platform.
|
||||
* Until we're able to decouple it, this key will serve as a workaround to
|
||||
* pass it configuration.
|
||||
*
|
||||
* @param {boolean} hideHandles Whether to show/hide annotation "handles"
|
||||
*/
|
||||
cornerstoneExtensionConfig: {
|
||||
/* Whether to show/hide annotation "handles" */
|
||||
hideHandles: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Viewer: Study Prefetcher & Stack Prefetch
|
||||
|
||||
Prefetching the stack of images of the active viewport is enabled by default for
|
||||
cornerstone viewports via cornerstone tools's stack prefetch functionality.
|
||||
|
||||
In order to have a more customizable alternative for prefetching, OHIF provides
|
||||
a built-in study prefetcher functionality which allows the user to set the order
|
||||
in which the display sets are prefetched, the granularity, the timeout and
|
||||
whether or not display a progress bar in the display set (close to the thumbnail
|
||||
image) in the study browser.
|
||||
|
||||
You can customize these options via the viewer's configuration:
|
||||
|
||||
```js
|
||||
window.config = {
|
||||
/**
|
||||
* OHIF's study prefetcher configuration.
|
||||
*
|
||||
* @param {boolean} enabled Whether to enable/disable OHIF's study prefetcher
|
||||
* @param {('all'|'closest'|'downward'|'upward'|'topdown')} order Fetching order: all display sets, the closest ones, downward or top down fashion based on the currently selected display set
|
||||
* @param {number} displaySetCount How much display sets should be prefetched at once (note: this attribute is ignored if order was set to 'all')
|
||||
* @param {boolean} preventCache Prevent images to be cached in Cornerstone Tools's request pool manager
|
||||
* @param {number} prefetchDisplaySetsTimeout Prefetch timeout
|
||||
* @param {boolean} displayProgress Whether to display or not the progress bar in the display set
|
||||
* @param {boolean} includeActiveDisplaySet Include or not the active display set while prefetching
|
||||
*/
|
||||
studyPrefetcher: {
|
||||
enabled: true,
|
||||
order: 'all',
|
||||
displaySetCount: 1,
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
displayProgress: false,
|
||||
includeActiveDisplaySet: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -3,6 +3,68 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.12.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.4...@ohif/extension-cornerstone@2.12.5) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.12.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.3...@ohif/extension-cornerstone@2.12.4) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.12.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.2...@ohif/extension-cornerstone@2.12.3) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.12.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.1...@ohif/extension-cornerstone@2.12.2) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.0...@ohif/extension-cornerstone@2.12.1) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.11.0...@ohif/extension-cornerstone@2.12.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.10...@ohif/extension-cornerstone@2.11.0) (2021-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.9...@ohif/extension-cornerstone@2.10.10) (2021-07-26)
|
||||
|
||||
|
||||
|
||||
@@ -85,12 +85,32 @@ Tools can be configured through extension configuration using the tools key:
|
||||
...
|
||||
```
|
||||
|
||||
## Stack prefetch configuration
|
||||
|
||||
Stack prefetch can be configured through extension configuration using the
|
||||
stackPrefetch key:
|
||||
|
||||
```js
|
||||
...
|
||||
cornerstoneExtensionConfig: {
|
||||
stackPrefetch: {
|
||||
enabled: true,
|
||||
maxImagesToPrefetch: Infinity,
|
||||
preserveExistingPool: false,
|
||||
maxSimultaneousRequests: 20,
|
||||
},
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
## Annotate Tools Configuration
|
||||
|
||||
*We currently support one property for annotation tools.*
|
||||
_We currently support one property for annotation tools._
|
||||
|
||||
### Hide handles
|
||||
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
|
||||
|
||||
This extension configuration allows you to toggle on/off handle rendering for
|
||||
all annotate tools:
|
||||
|
||||
```js
|
||||
...
|
||||
@@ -119,3 +139,4 @@ This extension configuration allows you to toggle on/off handle rendering for al
|
||||
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
|
||||
[cornerstone]: https://github.com/cornerstonejs/cornerstone
|
||||
<!-- prettier-ignore-end -->
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.10.10",
|
||||
"version": "2.12.5",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -32,12 +32,12 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"cornerstone-wado-image-loader": "^4.1.0",
|
||||
"dcmjs": "0.19.3",
|
||||
"dicom-parser": "^1.8.11",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
@@ -51,6 +51,6 @@
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-cornerstone-viewport": "4.0.5"
|
||||
"react-cornerstone-viewport": "4.1.1"
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,11 @@ const mapStateToProps = (state, ownProps) => {
|
||||
// Currently justing using escape hatch + commands
|
||||
// activeTool: activeButton && activeButton.command,
|
||||
...dataFromStore,
|
||||
isStackPrefetchEnabled: isActive,
|
||||
isStackPrefetchEnabled: ownProps.hasOwnProperty('isStackPrefetchEnabled')
|
||||
? ownProps.isStackPrefetchEnabled
|
||||
: ownProps.stackPrefetch
|
||||
? ownProps.stackPrefetch.enabled
|
||||
: isActive,
|
||||
isPlaying,
|
||||
frameRate,
|
||||
//stack: viewportSpecificData.stack,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
|
||||
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay';
|
||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||
import OHIF from '@ohif/core';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -15,6 +15,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
static defaultProps = {
|
||||
customProps: {},
|
||||
isStackPrefetchEnabled: true,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
@@ -23,6 +24,8 @@ class OHIFCornerstoneViewport extends Component {
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object,
|
||||
stackPrefetch: PropTypes.object,
|
||||
isStackPrefetchEnabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
static id = 'OHIFCornerstoneViewport';
|
||||
@@ -180,7 +183,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
@@ -232,7 +235,12 @@ class OHIFCornerstoneViewport extends Component {
|
||||
};
|
||||
|
||||
const warningsOverlay = props => {
|
||||
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} />
|
||||
return (
|
||||
<OHIFCornerstoneViewportOverlay
|
||||
{...props}
|
||||
inconsistencyWarnings={inconsistencyWarnings}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -244,10 +252,11 @@ class OHIFCornerstoneViewport extends Component {
|
||||
onNewImageDebounced={newImageHandler}
|
||||
onNewImageDebounceTime={300}
|
||||
viewportOverlayComponent={warningsOverlay}
|
||||
stackPrefetch={this.props.stackPrefetch}
|
||||
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
|
||||
// ~~ Connected (From REDUX)
|
||||
// frameRate={frameRate}
|
||||
// isPlaying={false}
|
||||
// isStackPrefetchEnabled={true}
|
||||
// onElementEnabled={() => {}}
|
||||
// setViewportActive{() => {}}
|
||||
{...this.props.customProps}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default {
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getViewportModule({ commandsManager }) {
|
||||
getViewportModule({ commandsManager, appConfig }) {
|
||||
const ExtendedOHIFCornerstoneViewport = props => {
|
||||
/**
|
||||
* TODO: This appears to be used to set the redux parameters for
|
||||
@@ -48,8 +48,17 @@ export default {
|
||||
jumpData.refreshViewports = false;
|
||||
commandsManager.runCommand('jumpToImage', jumpData);
|
||||
};
|
||||
|
||||
const { studyPrefetcher } = appConfig;
|
||||
const isStackPrefetchEnabled =
|
||||
studyPrefetcher && !studyPrefetcher.enabled;
|
||||
|
||||
return (
|
||||
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
|
||||
<OHIFCornerstoneViewport
|
||||
{...props}
|
||||
onNewImage={onNewImageHandler}
|
||||
isStackPrefetchEnabled={isStackPrefetchEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,14 @@ export default function init({ servicesManager, configuration }) {
|
||||
}
|
||||
};
|
||||
|
||||
const { csToolsConfig } = configuration;
|
||||
const {
|
||||
csToolsConfig,
|
||||
stackPrefetch = {
|
||||
maxImagesToPrefetch: Infinity,
|
||||
preserveExistingPool: false,
|
||||
maxSimultaneousRequests: 20,
|
||||
},
|
||||
} = configuration;
|
||||
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
@@ -52,7 +59,7 @@ export default function init({ servicesManager, configuration }) {
|
||||
autoResizeViewports: false,
|
||||
};
|
||||
|
||||
initCornerstoneTools(defaultCsToolsConfig);
|
||||
initCornerstoneTools({ ...defaultCsToolsConfig, ...stackPrefetch });
|
||||
|
||||
const toolsGroupedByType = {
|
||||
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
||||
|
||||
@@ -37,8 +37,8 @@ export default function(configuration = {}) {
|
||||
|
||||
// Configure stack prefetch
|
||||
cornerstoneTools.stackPrefetch.setConfiguration({
|
||||
maxImagesToPrefetch: Infinity,
|
||||
preserveExistingPool: false,
|
||||
maxSimultaneousRequests: 20,
|
||||
maxImagesToPrefetch: configuration.maxImagesToPrefetch,
|
||||
preserveExistingPool: configuration.preserveExistingPool,
|
||||
maxSimultaneousRequests: configuration.maxSimultaneousRequests,
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.3.0...@ohif/extension-debugging@0.3.1) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.2...@ohif/extension-debugging@0.3.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.2.1...@ohif/extension-debugging@0.2.2) (2021-06-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-debugging",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.1",
|
||||
"description": "OHIF extension for debugging.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -28,12 +28,12 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^2.6.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
"dicom-parser": "^1.8.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"detect-browser": "5.1.1",
|
||||
"dicomweb-client": "^0.8.1",
|
||||
"dicomweb-client": "^0.8.3",
|
||||
"file-saver": "^2.0.2",
|
||||
"jszip": "^3.2.2"
|
||||
}
|
||||
|
||||
@@ -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.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.15...@ohif/extension-dicom-html@1.3.16) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.14...@ohif/extension-dicom-html@1.3.15) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.13...@ohif/extension-dicom-html@1.3.14) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.12...@ohif/extension-dicom-html@1.3.13) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.11...@ohif/extension-dicom-html@1.3.12) (2021-10-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.10...@ohif/extension-dicom-html@1.3.11) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.9...@ohif/extension-dicom-html@1.3.10) (2021-06-02)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.3.10",
|
||||
"version": "1.3.16",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dcmjs": "0.19.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -160,7 +160,7 @@ const getContentSequence = (data, level = 1) => {
|
||||
|
||||
const root = [];
|
||||
if (header) {
|
||||
const HeaderDynamicLevel = `h${level}`;
|
||||
const HeaderDynamicLevel = `h${Math.min(level, 6)}`;
|
||||
|
||||
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
|
||||
}
|
||||
@@ -170,7 +170,7 @@ const getContentSequence = (data, level = 1) => {
|
||||
|
||||
if (key === '_meta') {
|
||||
const HeaderDynamicLevel = `h3`;
|
||||
root.push(<hr />);
|
||||
root.push(<hr key={root.length} />);
|
||||
root.push(
|
||||
<HeaderDynamicLevel key="Metadata">
|
||||
DICOM File Meta Information
|
||||
@@ -217,7 +217,7 @@ class DicomHtmlViewport extends Component {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const dataSet = this.setContentFromByteArray(this.props.byteArray);
|
||||
this.setContentFromByteArray(this.props.byteArray);
|
||||
}
|
||||
|
||||
setContentFromByteArray(byteArray) {
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
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-pdf@1.1.1...@ohif/extension-dicom-pdf@1.1.2) (2022-01-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.1.0...@ohif/extension-dicom-pdf@1.1.1) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.6...@ohif/extension-dicom-pdf@1.1.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.2",
|
||||
"description": "OHIF extension for Dicom PDF",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicom-parser": "^1.8.11",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -33,6 +33,7 @@ class DicomPDFViewport extends Component {
|
||||
|
||||
static propTypes = {
|
||||
byteArray: TypedArrayProp.uint8,
|
||||
rawPdf: PropTypes.bool,
|
||||
useNative: PropTypes.bool,
|
||||
viewportData: PropTypes.object,
|
||||
activeViewportIndex: PropTypes.number,
|
||||
@@ -45,7 +46,8 @@ class DicomPDFViewport extends Component {
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const dataSet = this.parseByteArray(this.props.byteArray);
|
||||
const { rawPdf } = this.props
|
||||
const dataSet = !rawPdf && this.parseByteArray(this.props.byteArray);
|
||||
const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray);
|
||||
|
||||
this.setState(state => ({ ...state, fileURL }));
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import OHIF from '@ohif/core';
|
||||
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
||||
import DicomPDFViewport from './DicomPDFViewport';
|
||||
import { str2ab } from '@ohif/core';
|
||||
|
||||
const { DicomLoaderService } = OHIF.utils;
|
||||
|
||||
@@ -18,6 +19,7 @@ class OHIFDicomPDFViewport extends Component {
|
||||
|
||||
state = {
|
||||
byteArray: null,
|
||||
rawPdf: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
@@ -34,6 +36,15 @@ class OHIFDicomPDFViewport extends Component {
|
||||
componentDidMount() {
|
||||
const { displaySet, studies } = this.props.viewportData;
|
||||
|
||||
if (displaySet.metadata && displaySet.metadata.EncapsulatedDocument) {
|
||||
const { InlineBinary, BulkDataURI } = displaySet.metadata.EncapsulatedDocument;
|
||||
if (InlineBinary) {
|
||||
const inlineBinaryData = atob(InlineBinary);
|
||||
const byteArray = str2ab(inlineBinaryData);
|
||||
this.setState({ byteArray, rawPdf: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||
data => this.setState({ byteArray: new Uint8Array(data) }),
|
||||
error => {
|
||||
@@ -49,15 +60,16 @@ class OHIFDicomPDFViewport extends Component {
|
||||
viewportIndex,
|
||||
activeViewportIndex,
|
||||
} = this.props;
|
||||
const { byteArray, error } = this.state;
|
||||
const { byteArray, error, rawPdf } = this.state;
|
||||
const { id, init, destroy } = OHIFDicomPDFViewport;
|
||||
const pluginProps = { id, init, destroy };
|
||||
|
||||
return (
|
||||
<OHIFComponentPlugin {...pluginProps}>
|
||||
{byteArray && (
|
||||
{(byteArray) && (
|
||||
<DicomPDFViewport
|
||||
byteArray={byteArray}
|
||||
rawPdf={rawPdf}
|
||||
setViewportActive={setViewportActive}
|
||||
viewportIndex={viewportIndex}
|
||||
activeViewportIndex={activeViewportIndex}
|
||||
|
||||
@@ -3,6 +3,81 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.7.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.6...@ohif/extension-dicom-rt@0.7.7) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.5...@ohif/extension-dicom-rt@0.7.6) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.4...@ohif/extension-dicom-rt@0.7.5) (2022-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.3...@ohif/extension-dicom-rt@0.7.4) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.2...@ohif/extension-dicom-rt@0.7.3) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.1...@ohif/extension-dicom-rt@0.7.2) (2021-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.0...@ohif/extension-dicom-rt@0.7.1) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.14...@ohif/extension-dicom-rt@0.7.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.13...@ohif/extension-dicom-rt@0.6.14) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.12...@ohif/extension-dicom-rt@0.6.13) (2021-06-02)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-rt",
|
||||
"version": "0.6.13",
|
||||
"version": "0.7.7",
|
||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,9 +29,9 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.18.8",
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"dcmjs": "0.19.3",
|
||||
"gl-matrix": "^3.3.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
|
||||
@@ -81,13 +81,10 @@ const RTPanel = ({
|
||||
useEffect(() => {
|
||||
updateStructureSets();
|
||||
|
||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||
document.addEventListener('rt-panel-tab-updated', updateStructureSets);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'extensiondicomrtrtloaded',
|
||||
updateStructureSets
|
||||
);
|
||||
document.removeEventListener('rt-panel-tab-updated', updateStructureSets);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
import init from './init.js';
|
||||
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
||||
import id from './id.js';
|
||||
import RTPanel from './components/RTPanel/RTPanel';
|
||||
import { version } from '../package.json';
|
||||
|
||||
import { utils } from '@ohif/core';
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
export default {
|
||||
@@ -42,29 +43,60 @@ export default {
|
||||
);
|
||||
};
|
||||
|
||||
const RTPanelTabChangedEvent = 'rt-panel-tab-updated';
|
||||
|
||||
/**
|
||||
* Trigger's an event to update the state of the panel's RoundedButtonGroup.
|
||||
*
|
||||
* This is required to avoid extension state
|
||||
* coupling with the viewer's ToolbarRow component.
|
||||
*
|
||||
* @param {object} data
|
||||
*/
|
||||
const triggerRTPanelUpdatedEvent = data => {
|
||||
const event = new CustomEvent(RTPanelTabChangedEvent, {
|
||||
detail: data,
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const onRTStructsLoaded = ({ detail }) => {
|
||||
const { rtStructDisplaySet, referencedDisplaySet } = detail;
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
rtStructDisplaySet.StudyInstanceUID
|
||||
);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: referencedDisplaySet.SeriesInstanceUID,
|
||||
Modality: 'RTSTRUCT',
|
||||
});
|
||||
triggerRTPanelUpdatedEvent({
|
||||
badgeNumber: referencedDisplaysets.length,
|
||||
target: 'rt-panel',
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('extensiondicomrtrtloaded', onRTStructsLoaded);
|
||||
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'RTSTRUCT',
|
||||
target: 'rt-panel',
|
||||
stateEvent: RTPanelTabChangedEvent,
|
||||
isDisabled: (studies, activeViewport) => {
|
||||
if (!studies) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (activeViewport) {
|
||||
const study = studies.find(
|
||||
s => s.StudyInstanceUID === activeViewport.StudyInstanceUID
|
||||
);
|
||||
const ds = study.displaySets.find(
|
||||
ds =>
|
||||
ds.displaySetInstanceUID ===
|
||||
activeViewport.displaySetInstanceUID
|
||||
);
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
if (!studyMetadata) {
|
||||
return;
|
||||
}
|
||||
const referencedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||
Modality: 'RTSTRUCT',
|
||||
@@ -75,6 +107,10 @@ export default {
|
||||
['RTSTRUCT'].includes(ds.Modality)
|
||||
)
|
||||
) {
|
||||
triggerRTPanelUpdatedEvent({
|
||||
badgeNumber: referencedDisplaySets.length,
|
||||
target: 'rt-panel',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ export default async function loadRTStruct(
|
||||
const sopInstanceUID = ContourImageSequence
|
||||
? ContourImageSequence.ReferencedSOPInstanceUID
|
||||
: _getClosestSOPInstanceUID(
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
);
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
);
|
||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||
|
||||
if (!imageId) {
|
||||
@@ -171,7 +171,13 @@ export default async function loadRTStruct(
|
||||
* allows us to easily watch the module or the rtstruct loading process in any other component
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
const event = new CustomEvent('extensiondicomrtrtloaded');
|
||||
const event = new CustomEvent('extensiondicomrtrtloaded', {
|
||||
detail: {
|
||||
rtStructDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies,
|
||||
},
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
@@ -299,7 +305,9 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||
);
|
||||
|
||||
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
||||
return imageIdSopInstanceUidPairsEntry
|
||||
? imageIdSopInstanceUidPairsEntry.imageId
|
||||
: null;
|
||||
};
|
||||
|
||||
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||
|
||||
@@ -3,6 +3,81 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.7.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.6...@ohif/extension-dicom-segmentation@0.7.7) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.5...@ohif/extension-dicom-segmentation@0.7.6) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.4...@ohif/extension-dicom-segmentation@0.7.5) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.3...@ohif/extension-dicom-segmentation@0.7.4) (2021-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.2...@ohif/extension-dicom-segmentation@0.7.3) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.1...@ohif/extension-dicom-segmentation@0.7.2) (2021-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.0...@ohif/extension-dicom-segmentation@0.7.1) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.11...@ohif/extension-dicom-segmentation@0.7.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.10...@ohif/extension-dicom-segmentation@0.6.11) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.9...@ohif/extension-dicom-segmentation@0.6.10) (2021-06-02)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.6.10",
|
||||
"version": "0.7.7",
|
||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,9 +29,9 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.18.8",
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"dcmjs": "0.19.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -24,6 +24,7 @@ const SegmentItem = ({
|
||||
onClick,
|
||||
itemClass,
|
||||
color,
|
||||
labelmap3D,
|
||||
visible,
|
||||
onVisibilityChange,
|
||||
}) => {
|
||||
@@ -39,7 +40,7 @@ const SegmentItem = ({
|
||||
event.stopPropagation();
|
||||
const newVisibility = !isVisible;
|
||||
setIsVisible(newVisibility);
|
||||
onVisibilityChange(newVisibility, index);
|
||||
onVisibilityChange(newVisibility, index, labelmap3D);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+46
-20
@@ -224,16 +224,20 @@ const SegmentationPanel = ({
|
||||
)
|
||||
);
|
||||
};
|
||||
}, [activeIndex, viewports]);
|
||||
}, [
|
||||
activeIndex,
|
||||
updateSegmentationComboBox,
|
||||
viewports,
|
||||
]);
|
||||
|
||||
const updateSegmentationComboBox = (e) => {
|
||||
const updateSegmentationComboBox = e => {
|
||||
const index = e.detail.activatedLabelmapIndex;
|
||||
if (index !== -1) {
|
||||
setState(state => ({ ...state, selectedSegmentation: index }));
|
||||
} else {
|
||||
cleanSegmentationComboBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanSegmentationComboBox = () => {
|
||||
setState(state => ({
|
||||
@@ -245,7 +249,7 @@ const SegmentationPanel = ({
|
||||
isDisabled: true,
|
||||
selectedSegmentation: -1,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const refreshSegmentations = () => {
|
||||
const activeViewport = getActiveViewport();
|
||||
@@ -309,7 +313,8 @@ const SegmentationPanel = ({
|
||||
);
|
||||
|
||||
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
||||
(segDisplay => segDisplay.loadError !== true));
|
||||
segDisplay => segDisplay.loadError !== true
|
||||
);
|
||||
|
||||
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
||||
const {
|
||||
@@ -426,23 +431,43 @@ const SegmentationPanel = ({
|
||||
return enabledElements[activeIndex].element;
|
||||
};
|
||||
|
||||
const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => {
|
||||
/** Get all labelmaps with this segmentNumber (overlapping segments) */
|
||||
const { labelmaps3D } = getBrushStackState();
|
||||
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
|
||||
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
|
||||
segmentsOnLabelmap.includes(segmentNumber)
|
||||
);
|
||||
});
|
||||
|
||||
const onSegmentVisibilityChangeHandler = (
|
||||
isVisible,
|
||||
segmentNumber,
|
||||
labelmap3D
|
||||
) => {
|
||||
let segmentsHidden = [];
|
||||
possibleLabelMaps3D.forEach(labelmap3D => {
|
||||
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
||||
if (labelmap3D.metadata.hasOverlapping) {
|
||||
/** Get all labelmaps with this segmentNumber and that
|
||||
* are from the same series (overlapping segments) */
|
||||
const { labelmaps3D } = getBrushStackState();
|
||||
|
||||
segmentsHidden = [
|
||||
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
|
||||
];
|
||||
});
|
||||
const sameSeriesLabelMaps3D = labelmaps3D.filter(({ metadata }) => {
|
||||
return (
|
||||
labelmap3D.metadata.segmentationSeriesInstanceUID ===
|
||||
metadata.segmentationSeriesInstanceUID
|
||||
);
|
||||
});
|
||||
|
||||
const possibleLabelMaps3D = sameSeriesLabelMaps3D.filter(
|
||||
({ labelmaps2D }) => {
|
||||
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
|
||||
segmentsOnLabelmap.includes(segmentNumber)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
possibleLabelMaps3D.forEach(labelmap3D => {
|
||||
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
||||
|
||||
segmentsHidden = [
|
||||
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
|
||||
];
|
||||
});
|
||||
} else {
|
||||
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
||||
segmentsHidden = [...labelmap3D.segmentsHidden];
|
||||
}
|
||||
|
||||
setState(state => ({ ...state, segmentsHidden }));
|
||||
|
||||
@@ -509,6 +534,7 @@ const SegmentationPanel = ({
|
||||
label={segmentLabel}
|
||||
index={segmentNumber}
|
||||
color={color}
|
||||
labelmap3D={labelmap3D}
|
||||
visible={!labelmap3D.segmentsHidden[segmentIndex]}
|
||||
onVisibilityChange={onSegmentVisibilityChangeHandler}
|
||||
/>
|
||||
|
||||
@@ -64,8 +64,17 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
metadata,
|
||||
};
|
||||
|
||||
segDisplaySet.getSourceDisplaySet = function(studies, activateLabelMap = true, onDisplaySetLoadFailureHandler) {
|
||||
return getSourceDisplaySet(studies, segDisplaySet, activateLabelMap, onDisplaySetLoadFailureHandler);
|
||||
segDisplaySet.getSourceDisplaySet = function(
|
||||
studies,
|
||||
activateLabelMap = true,
|
||||
onDisplaySetLoadFailureHandler
|
||||
) {
|
||||
return getSourceDisplaySet(
|
||||
studies,
|
||||
segDisplaySet,
|
||||
activateLabelMap,
|
||||
onDisplaySetLoadFailureHandler
|
||||
);
|
||||
};
|
||||
|
||||
segDisplaySet.load = async function(referencedDisplaySet, studies) {
|
||||
@@ -98,6 +107,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
if (labelmapBufferArray.length > 1) {
|
||||
let labelmapIndexes = [];
|
||||
for (let i = 0; i < labelmapBufferArray.length; ++i) {
|
||||
segMetadata.segmentationSeriesInstanceUID =
|
||||
segDisplaySet.SeriesInstanceUID;
|
||||
segMetadata.hasOverlapping = true;
|
||||
labelmapIndexes.push(
|
||||
await loadSegmentation(
|
||||
imageIds,
|
||||
@@ -118,6 +130,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
labelmapIndex = labelmapIndexes[0];
|
||||
console.warn('Overlapping segments!');
|
||||
} else {
|
||||
segMetadata.segmentationSeriesInstanceUID =
|
||||
segDisplaySet.SeriesInstanceUID;
|
||||
segMetadata.hasOverlapping = false;
|
||||
labelmapIndex = await loadSegmentation(
|
||||
imageIds,
|
||||
segDisplaySet,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import OHIF from '@ohif/core';
|
||||
|
||||
import init from './init.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||
import { version } from '../package.json';
|
||||
const { studyMetadataManager } = OHIF.utils;
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -79,13 +82,51 @@ export default {
|
||||
);
|
||||
};
|
||||
|
||||
const SegmentationPanelTabUpdatedEvent = 'segmentation-panel-tab-updated';
|
||||
|
||||
/**
|
||||
* Trigger's an event to update the state of the panel's RoundedButtonGroup.
|
||||
*
|
||||
* This is required to avoid extension state
|
||||
* coupling with the viewer's ToolbarRow component.
|
||||
*
|
||||
* @param {object} data
|
||||
*/
|
||||
const triggerSegmentationPanelTabUpdatedEvent = data => {
|
||||
const event = new CustomEvent(SegmentationPanelTabUpdatedEvent, {
|
||||
detail: data,
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const onSegmentationsLoaded = ({ detail }) => {
|
||||
const { segDisplaySet, segMetadata } = detail;
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
segDisplaySet.StudyInstanceUID
|
||||
);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: segMetadata.seriesInstanceUid,
|
||||
Modality: 'SEG',
|
||||
});
|
||||
triggerSegmentationPanelTabUpdatedEvent({
|
||||
badgeNumber: referencedDisplaysets.length,
|
||||
target: 'segmentation-panel',
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener(
|
||||
'extensiondicomsegmentationsegloaded',
|
||||
onSegmentationsLoaded
|
||||
);
|
||||
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'Segmentations',
|
||||
target: 'segmentation-panel',
|
||||
isDisabled: studies => {
|
||||
stateEvent: SegmentationPanelTabUpdatedEvent,
|
||||
isDisabled: (studies, activeViewport) => {
|
||||
if (!studies) {
|
||||
return true;
|
||||
}
|
||||
@@ -98,6 +139,23 @@ export default {
|
||||
const series = study.series[j];
|
||||
|
||||
if (series.Modality === 'SEG') {
|
||||
if (activeViewport) {
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
if (!studyMetadata) {
|
||||
return;
|
||||
}
|
||||
const referencedDS = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID:
|
||||
activeViewport.SeriesInstanceUID,
|
||||
Modality: 'SEG',
|
||||
});
|
||||
triggerSegmentationPanelTabUpdatedEvent({
|
||||
badgeNumber: referencedDS.length,
|
||||
target: 'segmentation-panel',
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,16 @@ export default async function loadSegmentation(
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
console.log('Segmentation loaded.');
|
||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded', {
|
||||
detail: {
|
||||
imageIds,
|
||||
segDisplaySet,
|
||||
labelmapBuffer,
|
||||
segMetadata,
|
||||
segmentsOnFrame,
|
||||
labelmapSegments,
|
||||
},
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
return labelmapIndex;
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.17...@ohif/extension-dicom-tag-browser@0.2.18) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.16...@ohif/extension-dicom-tag-browser@0.2.17) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.15...@ohif/extension-dicom-tag-browser@0.2.16) (2021-12-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.14...@ohif/extension-dicom-tag-browser@0.2.15) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.13...@ohif/extension-dicom-tag-browser@0.2.14) (2021-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.12...@ohif/extension-dicom-tag-browser@0.2.13) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.11...@ohif/extension-dicom-tag-browser@0.2.12) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.10...@ohif/extension-dicom-tag-browser@0.2.11) (2021-06-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-tag-browser",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.18",
|
||||
"description": "OHIF extension for checking DICOM headers.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -28,12 +28,12 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^2.6.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dcmjs": "0.19.3",
|
||||
"react": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dicomweb-client": "^0.8.1",
|
||||
"dicomweb-client": "^0.8.3",
|
||||
"moment": "2.24.0",
|
||||
"react-select": "^3.0.8"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
activeDisplaySetInstanceUID,
|
||||
setActiveDisplaySetInstanceUID,
|
||||
] = useState(displaySetInstanceUID);
|
||||
const [activeInstance, setActiveInstance] = useState(0);
|
||||
const [activeInstance, setActiveInstance] = useState(1);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [meta, setMeta] = useState('');
|
||||
const [instanceList, setInstanceList] = useState([]);
|
||||
@@ -51,20 +51,21 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
description: displayDate,
|
||||
onClick: () => {
|
||||
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
|
||||
setActiveInstance(0);
|
||||
setActiveInstance(1);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let metadata;
|
||||
const isImageStack = activeDisplaySet instanceof ImageSet;
|
||||
const isImageStack =
|
||||
activeDisplaySet instanceof ImageSet &&
|
||||
activeDisplaySet.isModalitySupported === true;
|
||||
|
||||
let selectedInstanceValue;
|
||||
let instanceList;
|
||||
|
||||
if (isImageStack) {
|
||||
const { images } = activeDisplaySet;
|
||||
const image = images[activeInstance];
|
||||
const image = images[activeInstance - 1];
|
||||
|
||||
instanceList = images.map((image, index) => {
|
||||
const metadata = image.getData().metadata;
|
||||
@@ -91,7 +92,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
setInstanceList(instanceList);
|
||||
setDisplaySetList(newDisplaySetList);
|
||||
setIsImageStack(isImageStack);
|
||||
}, [activeDisplaySetInstanceUID, activeInstance]);
|
||||
}, [activeDisplaySetInstanceUID, activeInstance, displaySets]);
|
||||
|
||||
const selectedDisplaySetValue = displaySetList.find(
|
||||
ds => ds.value === activeDisplaySetInstanceUID
|
||||
@@ -106,7 +107,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
showValue
|
||||
step={1}
|
||||
min={1}
|
||||
max={instanceList.length - 1}
|
||||
max={instanceList.length}
|
||||
value={activeInstance}
|
||||
valueRenderer={value => <p>Instance Number: {value}</p>}
|
||||
onChange={({ target }) => {
|
||||
|
||||
@@ -3,6 +3,145 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.12...@ohif/extension-vtk@1.12.13) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.11...@ohif/extension-vtk@1.12.12) (2022-01-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.10...@ohif/extension-vtk@1.12.11) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.9...@ohif/extension-vtk@1.12.10) (2022-01-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.8...@ohif/extension-vtk@1.12.9) (2021-12-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.7...@ohif/extension-vtk@1.12.8) (2021-12-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.6...@ohif/extension-vtk@1.12.7) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.5...@ohif/extension-vtk@1.12.6) (2021-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.4...@ohif/extension-vtk@1.12.5) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.3...@ohif/extension-vtk@1.12.4) (2021-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.2...@ohif/extension-vtk@1.12.3) (2021-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.1...@ohif/extension-vtk@1.12.2) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.12.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.0...@ohif/extension-vtk@1.12.1) (2021-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.12.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.18...@ohif/extension-vtk@1.12.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.11.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.17...@ohif/extension-vtk@1.11.18) (2021-09-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.11.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.16...@ohif/extension-vtk@1.11.17) (2021-09-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.11.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.15...@ohif/extension-vtk@1.11.16) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.11.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.14...@ohif/extension-vtk@1.11.15) (2021-08-30)
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.11.15",
|
||||
"version": "1.12.13",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -32,11 +32,11 @@
|
||||
"@ohif/core": "^0.50.0",
|
||||
"@ohif/i18n": "^0.50.0",
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"cornerstone-wado-image-loader": "^4.1.0",
|
||||
"dcmjs": "0.19.3",
|
||||
"dicom-parser": "^1.8.11",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"prop-types": "^15.7.2",
|
||||
@@ -50,14 +50,14 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-vtkjs-viewport": "^0.14.2"
|
||||
"react-vtkjs-viewport": "^0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.13.14",
|
||||
"@ohif/ui": "^1.9.2",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"@ohif/core": "^2.16.9",
|
||||
"@ohif/ui": "^1.10.5",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"cornerstone-wado-image-loader": "^4.1.0",
|
||||
"dicom-parser": "^1.8.11",
|
||||
"gh-pages": "^2.0.1",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
|
||||
+4
-2
@@ -56,6 +56,7 @@
|
||||
"@babel/preset-env": "^7.5.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@percy/cypress": "^2.2.0",
|
||||
"@webpack-cli/serve": "^1.6.1",
|
||||
"babel-eslint": "9.x",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-inline-react-svg": "1.1.0",
|
||||
@@ -104,8 +105,8 @@
|
||||
"stylus-loader": "^3.0.2",
|
||||
"terser-webpack-plugin": "^2.1.0",
|
||||
"webpack": "^4.35.2",
|
||||
"webpack-cli": "^3.3.5",
|
||||
"webpack-dev-server": "^3.7.2",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.3",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"workbox-webpack-plugin": "^5.0.0-beta.1",
|
||||
@@ -123,6 +124,7 @@
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"**/node-gyp": "8.4.1",
|
||||
"**/@babel/runtime": "7.5.5"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,141 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.16.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.8...@ohif/core@2.16.9) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.7...@ohif/core@2.16.8) (2022-01-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.6...@ohif/core@2.16.7) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.5...@ohif/core@2.16.6) (2022-01-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.4...@ohif/core@2.16.5) (2021-12-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* DICOMFileLoadingListener typo in method name ([#2647](https://github.com/OHIF/Viewers/issues/2647)) ([761d54f](https://github.com/OHIF/Viewers/commit/761d54fbbfb376c20ee15794ef7bb7111f64a06f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.3...@ohif/core@2.16.4) (2021-12-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* SeriesTime and SeriesDate are only set for the first series and undefined for the remaining ones ([#2476](https://github.com/OHIF/Viewers/issues/2476)) ([6a401c9](https://github.com/OHIF/Viewers/commit/6a401c9e5204cb4f7af6aa33190ad5a8d0879de6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.2...@ohif/core@2.16.3) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.1...@ohif/core@2.16.2) (2021-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.16.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.0...@ohif/core@2.16.1) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.16.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.15.1...@ohif/core@2.16.0) (2021-10-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Added support for WADO servers built with static hosting (e.g. S3) ([#2566](https://github.com/OHIF/Viewers/issues/2566)) ([a046c6c](https://github.com/OHIF/Viewers/commit/a046c6c9017a84e5ead71d5113b5e892333b4130))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.15.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.15.0...@ohif/core@2.15.1) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.15.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.14.1...@ohif/core@2.15.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.14.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.14.0...@ohif/core@2.14.1) (2021-09-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.14.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.15...@ohif/core@2.14.0) (2021-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.13.15](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.14...@ohif/core@2.13.15) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.13.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.13...@ohif/core@2.13.14) (2021-08-30)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.13.14",
|
||||
"version": "2.16.9",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -31,16 +31,16 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"cornerstone-wado-image-loader": "^4.1.0",
|
||||
"dicom-parser": "^1.8.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"ajv": "^6.10.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dicomweb-client": "^0.8.1",
|
||||
"dcmjs": "0.19.3",
|
||||
"dicomweb-client": "^0.8.3",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import dcmjs from 'dcmjs';
|
||||
import classes from '../classes';
|
||||
|
||||
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
|
||||
|
||||
const { LogManager } = classes;
|
||||
|
||||
/**
|
||||
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
|
||||
* measurementData format is a viewer specific format to be stored into the redux and consumed by other components
|
||||
@@ -20,9 +23,21 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
);
|
||||
|
||||
const { MeasurementReport } = dcmjs.adapters.Cornerstone;
|
||||
const storedMeasurementByToolType = MeasurementReport.generateToolState(
|
||||
dataset
|
||||
);
|
||||
|
||||
let storedMeasurementByToolType;
|
||||
try {
|
||||
storedMeasurementByToolType = MeasurementReport.generateToolState(dataset);
|
||||
} catch (error) {
|
||||
const seriesDescription = dataset.SeriesDescription || '';
|
||||
LogManager.publish(LogManager.EVENTS.OnLog, {
|
||||
title: `Failed to parse ${seriesDescription} measurement report`,
|
||||
type: 'warning',
|
||||
message: error.message || '',
|
||||
notify: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const measurementData = {};
|
||||
let measurementNumber = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import PubSub from './PubSub';
|
||||
|
||||
/** Log Events */
|
||||
export const LogEvents = Object.freeze({
|
||||
OnLog: 'onLog',
|
||||
});
|
||||
|
||||
/**
|
||||
* Log manager that implements pub/sub.
|
||||
* This manager can be used to send logs across different packages
|
||||
* using previously registered events.
|
||||
*/
|
||||
class LogManager extends PubSub {
|
||||
EVENTS = LogEvents;
|
||||
}
|
||||
|
||||
/** Singleton */
|
||||
export default new LogManager();
|
||||
@@ -0,0 +1,75 @@
|
||||
const _subscriptions = Symbol('subscriptions');
|
||||
const _lastSubscriptionId = Symbol('lastSubscriptionId');
|
||||
|
||||
/**
|
||||
* Class to implement publish/subscribe pattern
|
||||
*
|
||||
* @class
|
||||
* @classdesc Pub/sub mechanism
|
||||
*/
|
||||
export default class PubSub {
|
||||
constructor() {
|
||||
this[_subscriptions] = {};
|
||||
this[_lastSubscriptionId] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to event
|
||||
*
|
||||
* @param {string} eventName Event name
|
||||
* @param {Function} callback Callback function
|
||||
* @returns {void}
|
||||
*/
|
||||
subscribe(eventName, callback) {
|
||||
if (eventName === undefined) {
|
||||
throw new Error('Event name is required');
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Callback must be a function');
|
||||
}
|
||||
|
||||
if (!this[_subscriptions].hasOwnProperty(eventName)) {
|
||||
this[_subscriptions][eventName] = {};
|
||||
}
|
||||
|
||||
const subscriptionId = `sub${this[_lastSubscriptionId]++}`;
|
||||
this[_subscriptions][eventName][subscriptionId] = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a subscription
|
||||
*
|
||||
* @param {string} eventName Event name
|
||||
* @param {Function} [callback] Callback function
|
||||
* @returns {void}
|
||||
*/
|
||||
unsubscribe(eventName, callback) {
|
||||
const callbacks = this[_subscriptions][eventName] || {};
|
||||
for (let subscriptionId in callbacks) {
|
||||
if (!callback) {
|
||||
delete callbacks[subscriptionId];
|
||||
} else if (callbacks[subscriptionId] === callback) {
|
||||
delete callbacks[subscriptionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish event to all subscriptions
|
||||
*
|
||||
* @param {String} eventName Event name
|
||||
* @param {any} [payload] Data that will be published
|
||||
* @returns {void}
|
||||
*/
|
||||
publish(eventName, ...payload) {
|
||||
if (eventName === undefined) {
|
||||
throw new Error('Event name is required');
|
||||
}
|
||||
|
||||
const callbacks = this[_subscriptions][eventName] || {};
|
||||
for (let subscriptionId in callbacks) {
|
||||
callbacks[subscriptionId](...payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import {
|
||||
clearStudyLoadingProgress,
|
||||
setStudyLoadingProgress,
|
||||
} from '../redux/actions';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import StackManager from '../utils/StackManager';
|
||||
import { StudyPrefetcher } from './StudyPrefetcher';
|
||||
|
||||
class BaseLoadingListener {
|
||||
constructor(stack, options = {}) {
|
||||
this.id = BaseLoadingListener.getNewId();
|
||||
this.stack = stack;
|
||||
this.startListening();
|
||||
this.statsItemsLimit = options.statsItemsLimit || 2;
|
||||
this.stats = {
|
||||
items: [],
|
||||
@@ -60,6 +58,9 @@ class BaseLoadingListener {
|
||||
}
|
||||
|
||||
_getProgressId() {
|
||||
/**
|
||||
* TODO: The id key should be configurable.
|
||||
*/
|
||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||
return 'StackProgress:' + displaySetInstanceUID;
|
||||
}
|
||||
@@ -96,11 +97,18 @@ class BaseLoadingListener {
|
||||
class DICOMFileLoadingListener extends BaseLoadingListener {
|
||||
constructor(stack, options) {
|
||||
super(stack, options);
|
||||
|
||||
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandler.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this._dataSetUrl = this._getDataSetUrl(stack);
|
||||
this._lastLoaded = 0;
|
||||
|
||||
// Check how many instances has already been download (cached)
|
||||
this._checkCachedData();
|
||||
|
||||
this.startListening();
|
||||
}
|
||||
|
||||
_checkCachedData() {
|
||||
@@ -127,10 +135,6 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
||||
startListening() {
|
||||
const imageLoadProgressEventName = this._getImageLoadProgressEventName();
|
||||
|
||||
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this.stopListening();
|
||||
|
||||
cornerstone.events.addEventListener(
|
||||
@@ -200,17 +204,57 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
||||
}
|
||||
}
|
||||
|
||||
const StudyLoadingListenerEvents = {
|
||||
OnProgress: 'StudyLoadingListenerEvents.OnProgress',
|
||||
};
|
||||
|
||||
function promiseState(promise, callback) {
|
||||
// Symbols and RegExps are never content-equal
|
||||
var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/;
|
||||
|
||||
function notifyPendingOrResolved(value) {
|
||||
if (value === uniqueValue) {
|
||||
return callback('pending');
|
||||
} else {
|
||||
return callback('fulfilled');
|
||||
}
|
||||
}
|
||||
|
||||
function notifyRejected(reason) {
|
||||
return callback('rejected');
|
||||
}
|
||||
|
||||
var race = [promise, Promise.resolve(uniqueValue)];
|
||||
Promise.race(race).then(notifyPendingOrResolved, notifyRejected);
|
||||
}
|
||||
|
||||
class StackLoadingListener extends BaseLoadingListener {
|
||||
constructor(stack, options = {}) {
|
||||
options.statsItemsLimit = 20;
|
||||
|
||||
super(stack, options);
|
||||
|
||||
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
|
||||
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
|
||||
this.framesStatus = this._createArray(stack.imageIds.length, false);
|
||||
this.loadedCount = 0;
|
||||
|
||||
// Check how many instances has already been download (cached)
|
||||
this._checkCachedData();
|
||||
this._debouncedSetProgressData = debounce((...args) => {
|
||||
this._setProgressData(...args);
|
||||
|
||||
/** After checking cache, continue prefetch */
|
||||
const studyPrefetcher = StudyPrefetcher.getInstance();
|
||||
studyPrefetcher.prefetch(studyPrefetcher.getElement());
|
||||
}, 300);
|
||||
const debounced = true;
|
||||
this._checkCachedData(debounced);
|
||||
|
||||
this.startListening();
|
||||
}
|
||||
|
||||
_convertImageIdsArrayToMap(imageIds) {
|
||||
@@ -238,18 +282,37 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
return array;
|
||||
}
|
||||
|
||||
_checkCachedData() {
|
||||
// const imageIds = this.stack.imageIds;
|
||||
// TODO: No way to check status of Promise.
|
||||
/*for(let i = 0; i < imageIds.length; i++) {
|
||||
const imageId = imageIds[i];
|
||||
/**
|
||||
* Check if image id is cached in cornerstone.
|
||||
*
|
||||
* @param {string} imageId
|
||||
* @returns
|
||||
*/
|
||||
isImageCached(imageId) {
|
||||
const image = cornerstone.imageCache.imageCache[imageId];
|
||||
return image && image.sizeInBytes;
|
||||
}
|
||||
|
||||
const imagePromise = cornerstone.imageCache.getImageLoadObject(imageId).promise;
|
||||
_checkCachedData(debounced = false) {
|
||||
const imageIds = this.stack.imageIds;
|
||||
|
||||
if (imagePromise && (imagePromise.state() === 'resolved')) {
|
||||
this._updateFrameStatus(imageId, true);
|
||||
}
|
||||
}*/
|
||||
for (let i = 0; i < imageIds.length; i++) {
|
||||
const imageId = imageIds[i];
|
||||
|
||||
const imageObject = cornerstone.imageCache.getImageLoadObject(imageId);
|
||||
|
||||
if (this.isImageCached(imageId)) {
|
||||
this._updateFrameStatus(imageId, true, debounced);
|
||||
}
|
||||
|
||||
if (imageObject && imageObject.promise) {
|
||||
promiseState(imageObject.promise, state => {
|
||||
if (state === 'fulfilled') {
|
||||
this._updateFrameStatus(imageId, true, debounced);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getImageLoadedEventName() {
|
||||
@@ -272,11 +335,6 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
const imageLoadedEventName = this._getImageLoadedEventName();
|
||||
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
|
||||
|
||||
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
|
||||
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this.stopListening();
|
||||
|
||||
cornerstone.events.addEventListener(
|
||||
@@ -303,7 +361,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
);
|
||||
}
|
||||
|
||||
_updateFrameStatus(imageId, loaded) {
|
||||
_updateFrameStatus(imageId, loaded, debounced) {
|
||||
const imageData = this.imageDataMap.get(imageId);
|
||||
|
||||
if (!imageData || imageData.loaded === loaded) {
|
||||
@@ -318,7 +376,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
imageData.loaded = loaded;
|
||||
this.framesStatus[imageData.index] = loaded;
|
||||
this.loadedCount += loaded ? 1 : -1;
|
||||
this._updateProgress();
|
||||
this._updateProgress(debounced);
|
||||
}
|
||||
|
||||
_setProgressData(progressId, progressData) {
|
||||
@@ -339,7 +397,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
);
|
||||
}
|
||||
|
||||
_updateProgress() {
|
||||
_updateProgress(debounced) {
|
||||
const totalFramesCount = this.stack.imageIds.length;
|
||||
const loadedFramesCount = this.loadedCount;
|
||||
const loadingFramesCount = totalFramesCount - loadedFramesCount;
|
||||
@@ -357,6 +415,11 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
framesStatus: this.framesStatus,
|
||||
};
|
||||
|
||||
if (debounced) {
|
||||
this._debouncedSetProgressData(progressId, progressData);
|
||||
return;
|
||||
}
|
||||
|
||||
this._setProgressData(progressId, progressData);
|
||||
}
|
||||
|
||||
@@ -371,11 +434,13 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
}
|
||||
|
||||
progressBar += ']';
|
||||
log.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||
console.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||
}
|
||||
}
|
||||
|
||||
class StudyLoadingListener {
|
||||
static events = StudyLoadingListenerEvents;
|
||||
|
||||
constructor(options) {
|
||||
this.listeners = {};
|
||||
this.options = options;
|
||||
@@ -454,14 +519,36 @@ class StudyLoadingListener {
|
||||
|
||||
_getSchema(stack) {
|
||||
const imageId = stack.imageIds[0];
|
||||
if (!imageId) {
|
||||
return;
|
||||
}
|
||||
const colonIndex = imageId.indexOf(':');
|
||||
return imageId.substring(0, colonIndex);
|
||||
}
|
||||
|
||||
// Singleton
|
||||
static getInstance(options) {
|
||||
/**
|
||||
* TODO: Use a different alternative without the use of events.
|
||||
*/
|
||||
const DEFAULT_OPTIONS = {
|
||||
_setProgressData: (progressId, progressData) => {
|
||||
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
|
||||
detail: { progressId, progressData },
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
_clearProgressById: progressId => {
|
||||
const event = new CustomEvent(StudyLoadingListenerEvents.OnProgress, {
|
||||
detail: { progressId, percentComplete: 0 },
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
};
|
||||
|
||||
if (!StudyLoadingListener._instance) {
|
||||
StudyLoadingListener._instance = new StudyLoadingListener(options);
|
||||
StudyLoadingListener._instance = new StudyLoadingListener(
|
||||
options || DEFAULT_OPTIONS
|
||||
);
|
||||
}
|
||||
|
||||
return StudyLoadingListener._instance;
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import log from '../log.js';
|
||||
import OHIFError from './OHIFError';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import getImageId from '../utils/getImageId.js';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export class StudyPrefetcher {
|
||||
constructor(studies) {
|
||||
options = {
|
||||
order: 'closest',
|
||||
displaySetCount: 1,
|
||||
onImageCached: noop,
|
||||
requestType: 'prefetch',
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
maxNumPrefetchRequests: 100,
|
||||
includeActiveDisplaySet: false,
|
||||
};
|
||||
|
||||
constructor(studies, options) {
|
||||
this.studies = studies || [];
|
||||
this.prefetchDisplaySetsTimeout = 300;
|
||||
this.lastActiveViewportElement = null;
|
||||
|
||||
if (options) {
|
||||
this.options = { ...this.options, ...options };
|
||||
this.options.requestType = 'prefetch';
|
||||
}
|
||||
|
||||
cornerstone.events.addEventListener(
|
||||
'cornerstoneimagecachefull.StudyPrefetcher',
|
||||
@@ -16,6 +29,9 @@ export class StudyPrefetcher {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove previously set event listeners and stop prefetching.
|
||||
*/
|
||||
destroy() {
|
||||
this.stopPrefetching();
|
||||
cornerstone.events.removeEventListener(
|
||||
@@ -24,95 +40,180 @@ export class StudyPrefetcher {
|
||||
);
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
/**
|
||||
* Get StudyPrefetcher singleton instance.
|
||||
*
|
||||
* @param {array} studies
|
||||
* @param {object} options
|
||||
* @returns
|
||||
*/
|
||||
static getInstance(studies = [], options) {
|
||||
if (!StudyPrefetcher.instance) {
|
||||
StudyPrefetcher.instance = new StudyPrefetcher([]);
|
||||
StudyPrefetcher.instance = new StudyPrefetcher(studies, options);
|
||||
}
|
||||
|
||||
if (options) {
|
||||
this.options = { ...this.options, ...options };
|
||||
this.options.requestType = 'prefetch';
|
||||
}
|
||||
|
||||
return StudyPrefetcher.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* OHIF study metadata instances.
|
||||
*
|
||||
* @param {array} studies
|
||||
*/
|
||||
setStudies(studies) {
|
||||
this.stopPrefetching();
|
||||
this.studies = studies;
|
||||
}
|
||||
|
||||
prefetch() {
|
||||
/**
|
||||
* Get previously prefetched element.
|
||||
*
|
||||
* @returns {HTMLElement} Previously prefetched element.
|
||||
*/
|
||||
getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch related display sets based on cornerstone viewport element
|
||||
* with previously set options.
|
||||
*
|
||||
* @param {*} element
|
||||
* @param {string} displaySetInstanceUID the display set instance uid
|
||||
* @returns
|
||||
*/
|
||||
prefetch(element, displaySetInstanceUID) {
|
||||
if (!this.studies || !this.studies.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.element = element;
|
||||
try {
|
||||
this.enabledElement = cornerstone.getEnabledElement(element);
|
||||
} catch {
|
||||
throw new Error('Failed to find the enabled element');
|
||||
}
|
||||
|
||||
this.stopPrefetching();
|
||||
this.prefetchDisplaySets();
|
||||
this.prefetchDisplaySets(displaySetInstanceUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop prefetching images.
|
||||
*/
|
||||
stopPrefetching() {
|
||||
cornerstoneTools.requestPoolManager.clearRequestStack('prefetch');
|
||||
cornerstone.imageLoadPoolManager.clearRequestStack('prefetch');
|
||||
}
|
||||
|
||||
prefetchDisplaySetsAsync(timeout) {
|
||||
timeout = timeout || this.prefetchDisplaySetsTimeout;
|
||||
|
||||
/**
|
||||
* Prefetch display sets async.
|
||||
*
|
||||
* @param {HTMLElement} element cornerstone viewport element
|
||||
* @param {number} timeout
|
||||
*/
|
||||
prefetchDisplaySetsAsync(element, timeout) {
|
||||
try {
|
||||
this.enabledElement = cornerstone.getEnabledElement(element);
|
||||
} catch {
|
||||
throw new Error('Failed to find the enabled element');
|
||||
}
|
||||
timeout = timeout || this.options.prefetchDisplaySetsTimeout;
|
||||
clearTimeout(this.prefetchDisplaySetsHandler);
|
||||
this.prefetchDisplaySetsHandler = setTimeout(() => {
|
||||
this.prefetchDisplaySets();
|
||||
this.prefetchDisplaySets(element);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
prefetchDisplaySets() {
|
||||
// TODO: Allow passing in config
|
||||
let config = {
|
||||
order: 'closest',
|
||||
displaySetCount: 1,
|
||||
};
|
||||
|
||||
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config);
|
||||
/**
|
||||
* Extract all image ids from all display sets to be fetched and
|
||||
* call method to add images to request pool manager.
|
||||
*
|
||||
* @param {string} displaySetInstanceUID the display set instance uid
|
||||
*/
|
||||
prefetchDisplaySets(displaySetInstanceUID) {
|
||||
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
|
||||
|
||||
this.prefetchImageIds(imageIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image ids to request pool manager.
|
||||
*
|
||||
* @param {array} imageIds
|
||||
*/
|
||||
prefetchImageIds(imageIds) {
|
||||
const nonCachedImageIds = this.filterCachedImageIds(imageIds);
|
||||
const requestPoolManager = cornerstoneTools.requestPoolManager;
|
||||
const requestType = 'prefetch';
|
||||
const preventCache = false;
|
||||
const noop = () => {};
|
||||
const imageLoadPoolManager = cornerstone.imageLoadPoolManager;
|
||||
|
||||
imageLoadPoolManager.maxNumRequests = {
|
||||
...imageLoadPoolManager.maxNumRequests,
|
||||
prefetch: this.options.maxNumPrefetchRequests,
|
||||
};
|
||||
|
||||
let requestFn;
|
||||
if (this.options.preventCache) {
|
||||
requestFn = id => cornerstone.loadImage(id);
|
||||
} else {
|
||||
requestFn = id => cornerstone.loadAndCacheImage(id);
|
||||
}
|
||||
|
||||
nonCachedImageIds.forEach(imageId => {
|
||||
requestPoolManager.addRequest(
|
||||
{},
|
||||
imageId,
|
||||
requestType,
|
||||
preventCache,
|
||||
noop,
|
||||
noop
|
||||
imageLoadPoolManager.addRequest(
|
||||
requestFn.bind(this, imageId),
|
||||
this.options.requestType,
|
||||
{
|
||||
imageId,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
requestPoolManager.startGrabbing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get study by cornerstone image instance.
|
||||
*
|
||||
* @param {object} image
|
||||
* @returns
|
||||
*/
|
||||
getStudy(image) {
|
||||
const StudyInstanceUID = cornerstone.metaData.get(
|
||||
'StudyInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
return OHIF.viewer.Studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
const studies = this.studies;
|
||||
return studies.find(
|
||||
study => study.getData().StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get study series by cornerstone image instance.
|
||||
*
|
||||
* @param {object} study OHIF study instance
|
||||
* @param {object} image cornerstone image instance object
|
||||
* @returns
|
||||
*/
|
||||
getSeries(study, image) {
|
||||
const SeriesInstanceUID = cornerstone.metaData.get(
|
||||
'SeriesInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
|
||||
|
||||
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||
return study.getSeriesByUID(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sop instance by cornerstone image instance.
|
||||
*
|
||||
* @param {array} series
|
||||
* @param {object} image
|
||||
* @returns
|
||||
*/
|
||||
getInstance(series, image) {
|
||||
const instanceMetadata = cornerstone.metaData.get(
|
||||
'instance',
|
||||
@@ -121,7 +222,33 @@ export class StudyPrefetcher {
|
||||
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
|
||||
}
|
||||
|
||||
getActiveDisplaySet(displaySets, instance) {
|
||||
/**
|
||||
* Returns the display set with given uid.
|
||||
*
|
||||
* @param {string} displaySetInstanceUID the display set instance uid
|
||||
* @returns {object} displaySet
|
||||
*/
|
||||
getDisplaySetByUID(displaySetInstanceUID) {
|
||||
let displaySet;
|
||||
this.studies.forEach(study => {
|
||||
const ds = study.displaySets.find(
|
||||
ds => ds.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
if (ds) {
|
||||
displaySet = ds;
|
||||
}
|
||||
});
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display set by SOPInstanceUID.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} instance
|
||||
* @returns
|
||||
*/
|
||||
getDisplaySetBySOPInstanceUID(displaySets, instance) {
|
||||
return displaySets.find(displaySet => {
|
||||
return displaySet.images.some(displaySetImage => {
|
||||
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
|
||||
@@ -129,25 +256,48 @@ export class StudyPrefetcher {
|
||||
});
|
||||
}
|
||||
|
||||
getDisplaySetsToPrefetch(config) {
|
||||
/**
|
||||
* Get active viewport image based on cornerstone viewport element.
|
||||
* @returns
|
||||
*/
|
||||
getActiveViewportImage() {
|
||||
if (!this.enabledElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.enabledElement.image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch display sets based on cornerstone viewport element image.
|
||||
*
|
||||
* @param {string} displaySetInstanceUID the display set instance uid
|
||||
* @returns {array} displaySets
|
||||
*/
|
||||
getDisplaySetsToPrefetch(displaySetInstanceUID) {
|
||||
const image = this.getActiveViewportImage();
|
||||
|
||||
if (!image || !config || !config.displaySetCount) {
|
||||
if (!image) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/*const study = this.getStudy(image);
|
||||
const study = this.getStudy(image);
|
||||
const series = this.getSeries(study, image);
|
||||
const instance = this.getInstance(series, image);*/
|
||||
const instance = this.getInstance(series, image);
|
||||
const displaySets = study.displaySets;
|
||||
const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance);
|
||||
const activeDisplaySet = displaySetInstanceUID
|
||||
? this.getDisplaySetByUID(displaySetInstanceUID)
|
||||
: this.getDisplaySetBySOPInstanceUID(displaySets, instance);
|
||||
|
||||
const prefetchMethodMap = {
|
||||
topdown: 'getFirstDisplaySets',
|
||||
downward: 'getNextDisplaySets',
|
||||
upward: 'getPreviousDisplaySets',
|
||||
closest: 'getClosestDisplaySets',
|
||||
all: 'getAllDisplaySets',
|
||||
};
|
||||
|
||||
const prefetchOrder = config.order;
|
||||
const prefetchOrder = this.options.order;
|
||||
const methodName = prefetchMethodMap[prefetchOrder];
|
||||
const getDisplaySets = this[methodName];
|
||||
|
||||
@@ -163,18 +313,59 @@ export class StudyPrefetcher {
|
||||
this,
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
config.displaySetCount
|
||||
this.options.displaySetCount,
|
||||
this.options.includeActiveDisplaySet
|
||||
);
|
||||
}
|
||||
|
||||
getFirstDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
|
||||
/**
|
||||
* Get all display sets.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} activeDisplaySet
|
||||
* @param {number} displaySetCount
|
||||
* @param {boolean} includeActiveDisplaySet
|
||||
* @returns
|
||||
*/
|
||||
getAllDisplaySets(
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
displaySetCount,
|
||||
includeActiveDisplaySet
|
||||
) {
|
||||
const length = displaySets.length;
|
||||
const selectedDisplaySets = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const displaySet = displaySets[i];
|
||||
selectedDisplaySets.push(displaySet);
|
||||
}
|
||||
|
||||
return selectedDisplaySets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all display sets in order after the active display set.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} activeDisplaySet
|
||||
* @param {number} displaySetCount
|
||||
* @param {boolean} includeActiveDisplaySet
|
||||
* @returns
|
||||
*/
|
||||
getFirstDisplaySets(
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
displaySetCount,
|
||||
includeActiveDisplaySet
|
||||
) {
|
||||
const length = displaySets.length;
|
||||
const selectedDisplaySets = [];
|
||||
|
||||
for (let i = 0; i < length && displaySetCount; i++) {
|
||||
const displaySet = displaySets[i];
|
||||
|
||||
if (displaySet !== activeDisplaySet) {
|
||||
if (includeActiveDisplaySet || displaySet !== activeDisplaySet) {
|
||||
selectedDisplaySets.push(displaySet);
|
||||
displaySetCount--;
|
||||
}
|
||||
@@ -183,21 +374,78 @@ export class StudyPrefetcher {
|
||||
return selectedDisplaySets;
|
||||
}
|
||||
|
||||
getNextDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
|
||||
/**
|
||||
* Get all display sets before the active display set.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} activeDisplaySet
|
||||
* @param {number} displaySetCount
|
||||
* @param {boolean} includeActiveDisplaySet
|
||||
* @returns
|
||||
*/
|
||||
getPreviousDisplaySets(
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
displaySetCount,
|
||||
includeActiveDisplaySet
|
||||
) {
|
||||
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
|
||||
const begin = activeDisplaySetIndex + 1;
|
||||
const end = Math.min(begin + displaySetCount, displaySets.length);
|
||||
const end = includeActiveDisplaySet
|
||||
? activeDisplaySetIndex + 1
|
||||
: activeDisplaySetIndex;
|
||||
const previousDisplaySets = displaySets.slice(0, end);
|
||||
return previousDisplaySets.reverse().slice(0, displaySetCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all display sets after the active display set.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} activeDisplaySet
|
||||
* @param {number} displaySetCount
|
||||
* @param {boolean} includeActiveDisplaySet
|
||||
* @returns
|
||||
*/
|
||||
getNextDisplaySets(
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
displaySetCount,
|
||||
includeActiveDisplaySet
|
||||
) {
|
||||
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
|
||||
const begin = includeActiveDisplaySet
|
||||
? activeDisplaySetIndex
|
||||
: activeDisplaySetIndex + 1;
|
||||
const end = Math.min(begin + displaySetCount, displaySets.length);
|
||||
return displaySets.slice(begin, end);
|
||||
}
|
||||
|
||||
getClosestDisplaySets(displaySets, activeDisplaySet, displaySetCount) {
|
||||
/**
|
||||
* Get all display set closest to the active display set.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @param {object} activeDisplaySet
|
||||
* @param {number} displaySetCount
|
||||
* @param {boolean} includeActiveDisplaySet
|
||||
* @returns
|
||||
*/
|
||||
getClosestDisplaySets(
|
||||
displaySets,
|
||||
activeDisplaySet,
|
||||
displaySetCount,
|
||||
includeActiveDisplaySet
|
||||
) {
|
||||
const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
|
||||
const length = displaySets.length;
|
||||
const selectedDisplaySets = [];
|
||||
let left = activeDisplaySetIndex - 1;
|
||||
let right = activeDisplaySetIndex + 1;
|
||||
|
||||
if (includeActiveDisplaySet) {
|
||||
selectedDisplaySets.push(displaySets[activeDisplaySetIndex]);
|
||||
displaySetCount--;
|
||||
}
|
||||
|
||||
while ((left >= 0 || right < length) && displaySetCount) {
|
||||
if (left >= 0) {
|
||||
selectedDisplaySets.push(displaySets[left]);
|
||||
@@ -215,6 +463,12 @@ export class StudyPrefetcher {
|
||||
return selectedDisplaySets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all image ids from display sets.
|
||||
*
|
||||
* @param {array} displaySets
|
||||
* @returns {array} image ids
|
||||
*/
|
||||
getImageIdsFromDisplaySets(displaySets) {
|
||||
let imageIds = [];
|
||||
|
||||
@@ -225,9 +479,19 @@ export class StudyPrefetcher {
|
||||
return imageIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all image ids from a given display set.
|
||||
*
|
||||
* @param {array} displaySet
|
||||
* @returns
|
||||
*/
|
||||
getImageIdsFromDisplaySet(displaySet) {
|
||||
const imageIds = [];
|
||||
|
||||
if (!displaySet.images || displaySet.images.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// TODO: This duplicates work done by the stack manager
|
||||
displaySet.images.forEach(image => {
|
||||
const numFrames = image.numFrames;
|
||||
@@ -245,15 +509,30 @@ export class StudyPrefetcher {
|
||||
return imageIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter cached image ids from a set of image ids.
|
||||
*
|
||||
* @param {array} imageIds
|
||||
* @returns {array} images not cached
|
||||
*/
|
||||
filterCachedImageIds(imageIds) {
|
||||
return imageIds.filter(imageId => !this.isImageCached(imageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if image id is cached in cornerstone.
|
||||
*
|
||||
* @param {string} imageId
|
||||
* @returns
|
||||
*/
|
||||
isImageCached(imageId) {
|
||||
const image = cornerstone.imageCache.imageCache[imageId];
|
||||
return image && image.sizeInBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warns that cache is full and stops prefetching.
|
||||
*/
|
||||
cacheFullHandler = () => {
|
||||
log.warn('Cache full');
|
||||
this.stopPrefetching();
|
||||
|
||||
@@ -4,6 +4,8 @@ import CommandsManager from './CommandsManager.js';
|
||||
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
||||
import HotkeysManager from './HotkeysManager.js';
|
||||
import ImageSet from './ImageSet';
|
||||
import LogManager from './LogManager';
|
||||
import PubSub from './PubSub';
|
||||
import MetadataProvider from './MetadataProvider';
|
||||
import OHIFError from './OHIFError.js';
|
||||
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
||||
@@ -36,7 +38,9 @@ const classes = {
|
||||
MetadataProvider,
|
||||
CommandsManager,
|
||||
HotkeysManager,
|
||||
LogManager,
|
||||
ImageSet,
|
||||
PubSub,
|
||||
StudyPrefetcher,
|
||||
StudyLoadingListener,
|
||||
StackLoadingListener,
|
||||
|
||||
@@ -9,7 +9,10 @@ import { SeriesMetadata } from './SeriesMetadata';
|
||||
import { api } from 'dicomweb-client';
|
||||
// - createStacks
|
||||
import { isImage } from '../../utils/isImage';
|
||||
import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable';
|
||||
import {
|
||||
isDisplaySetReconstructable,
|
||||
isSpacingUniform,
|
||||
} from '../../utils/isDisplaySetReconstructable';
|
||||
import errorHandler from '../../errorHandler';
|
||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
|
||||
@@ -159,41 +162,64 @@ class StudyMetadata extends Metadata {
|
||||
// series into another display set.
|
||||
const stackableInstances = [];
|
||||
series.forEachInstance(instance => {
|
||||
let displaySet;
|
||||
|
||||
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
|
||||
if (
|
||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||
!instance.getTagValue('Rows')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let displaySet;
|
||||
|
||||
if (isMultiFrame(instance)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
|
||||
// we set an empty display and we add a isModalitySupported variable to
|
||||
// print a warning that the modality is not supported in the thumbnail.
|
||||
const displaySet = new ImageSet([]);
|
||||
const seriesData = series.getData();
|
||||
displaySet.setAttributes({
|
||||
sopClassUIDs,
|
||||
isClip: true,
|
||||
displaySetInstanceUID: displaySet.uid,
|
||||
SeriesDate: seriesData.SeriesDate,
|
||||
SeriesTime: seriesData.SeriesTime,
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
||||
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else if (isSingleImageModality(instance.Modality)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
displaySet.setAttributes({
|
||||
sopClassUIDs,
|
||||
SeriesNumber: instance.getTagValue('SeriesNumber'),
|
||||
SeriesDescription: instance.getTagValue('SeriesDescription'),
|
||||
numImageFrames: instance.getTagValue('NumberOfFrames'),
|
||||
frameRate: instance.getTagValue('FrameTime'),
|
||||
Modality: instance.getTagValue('Modality'),
|
||||
isMultiFrame: false,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
isReconstructable: false,
|
||||
isModalitySupported: false,
|
||||
metadata: instance.getData().metadata,
|
||||
});
|
||||
|
||||
displaySets.push(displaySet);
|
||||
} else {
|
||||
stackableInstances.push(instance);
|
||||
if (isMultiFrame(instance)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
|
||||
displaySet.setAttributes({
|
||||
sopClassUIDs,
|
||||
isClip: true,
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
||||
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else if (isSingleImageModality(instance.Modality)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
displaySet.setAttributes({
|
||||
sopClassUIDs,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else {
|
||||
stackableInstances.push(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -227,7 +253,6 @@ class StudyMetadata extends Metadata {
|
||||
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the source display set of the derivated display set.
|
||||
* @param {object} derivatedDisplaySet
|
||||
@@ -242,40 +267,45 @@ class StudyMetadata extends Metadata {
|
||||
});
|
||||
|
||||
const otherDisplaySets = allDisplaySets.filter(
|
||||
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
|
||||
ds =>
|
||||
ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
const { metadata } = derivatedDisplaySet;
|
||||
|
||||
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
|
||||
(metadata, otherDisplaySets);
|
||||
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
|
||||
metadata,
|
||||
otherDisplaySets
|
||||
);
|
||||
|
||||
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
|
||||
let noReferencedSeriesAvailable =
|
||||
!referencedSeriesInstanceUIDs ||
|
||||
referencedSeriesInstanceUIDs.length === 0;
|
||||
if (noReferencedSeriesAvailable) {
|
||||
referencedSeriesInstanceUIDs =
|
||||
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
|
||||
(metadata);
|
||||
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
|
||||
metadata
|
||||
);
|
||||
}
|
||||
|
||||
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
|
||||
noReferencedSeriesAvailable =
|
||||
!referencedSeriesInstanceUIDs ||
|
||||
referencedSeriesInstanceUIDs.length === 0;
|
||||
if (noReferencedSeriesAvailable) {
|
||||
referencedSeriesInstanceUIDs =
|
||||
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
|
||||
(metadata, otherDisplaySets);
|
||||
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
|
||||
metadata,
|
||||
otherDisplaySets
|
||||
);
|
||||
}
|
||||
|
||||
const referencedSeriesAvailable = referencedSeriesInstanceUIDs &&
|
||||
referencedSeriesInstanceUIDs.length !== 0;
|
||||
const referencedSeriesAvailable =
|
||||
referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0;
|
||||
if (referencedSeriesAvailable) {
|
||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||
);
|
||||
;
|
||||
return referencedDisplaySet;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of derived datasets in the study, filtered by the given filter.
|
||||
@@ -303,7 +333,10 @@ class StudyMetadata extends Metadata {
|
||||
if (referencedSeriesInstanceUID) {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet => {
|
||||
return StudyMetadata.getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID;
|
||||
return (
|
||||
StudyMetadata.getReferencedDisplaySet(displaySet, [this])
|
||||
.SeriesInstanceUID === referencedSeriesInstanceUID
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -311,8 +344,7 @@ class StudyMetadata extends Metadata {
|
||||
if (referencedFrameOfReferenceUID) {
|
||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||
displaySet =>
|
||||
displaySet.FrameOfReferenceUID ===
|
||||
referencedFrameOfReferenceUID
|
||||
displaySet.FrameOfReferenceUID === referencedFrameOfReferenceUID
|
||||
);
|
||||
}
|
||||
|
||||
@@ -497,6 +529,14 @@ class StudyMetadata extends Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this._displaySets.some(
|
||||
ds => ds.displaySetInstanceUID === displaySet.displaySetInstanceUID
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._displaySets.splice(insertIndex, 0, displaySet);
|
||||
this.displaySets = this._displaySets;
|
||||
}
|
||||
@@ -861,8 +901,9 @@ const makeDisplaySet = (series, instances) => {
|
||||
imageSet.sortByImagePositionPatient();
|
||||
|
||||
// check if the spacing is uniform and update isReconstructable
|
||||
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find
|
||||
(issue => issue === ReconstructionIssues.DATASET_4D);
|
||||
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find(
|
||||
issue => issue === ReconstructionIssues.DATASET_4D
|
||||
);
|
||||
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
|
||||
imageSet.isReconstructable = displaySpacingInfo.isUniform;
|
||||
|
||||
@@ -875,11 +916,15 @@ const makeDisplaySet = (series, instances) => {
|
||||
|
||||
if (!imageSet.displayReconstructableInfo) {
|
||||
// It is not reconstrabale Save type of warning
|
||||
imageSet.reconstructionIssues = displaySpacingInfo ?
|
||||
displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) :
|
||||
displayReconstructableInfo.reconstructionIssues;
|
||||
imageSet.reconstructionIssues = displaySpacingInfo
|
||||
? displayReconstructableInfo.reconstructionIssues.concat(
|
||||
displaySpacingInfo.reconstructionIssues
|
||||
)
|
||||
: displayReconstructableInfo.reconstructionIssues;
|
||||
}
|
||||
|
||||
imageSet.isModalitySupported = true;
|
||||
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
@@ -957,38 +1002,36 @@ function _getDisplaySetFromSopClassModule(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* ReferencedSeriesSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence (
|
||||
metadata,
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* ReferencedSeriesSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
|
||||
metadata
|
||||
) {
|
||||
if (!metadata.ReferencedSeriesSequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
let referencedSeriesInstanceUIDs;
|
||||
const ReferencedSeriesSequence = _toArray(
|
||||
metadata.ReferencedSeriesSequence
|
||||
);
|
||||
const ReferencedSeriesSequence = _toArray(metadata.ReferencedSeriesSequence);
|
||||
|
||||
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
|
||||
return referencedSeriesInstanceUIDs;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* ReferencedImageSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @param {array[object]} displaysets
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* ReferencedImageSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @param {array[object]} displaysets
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
|
||||
metadata,
|
||||
displaySets
|
||||
) {
|
||||
@@ -1009,22 +1052,25 @@ function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
|
||||
ReferencedSOPInstanceUID
|
||||
);
|
||||
|
||||
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
|
||||
if (
|
||||
referencedSeriesInstanceUIDs &&
|
||||
referencedSeriesInstanceUIDs.length !== 0
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return referencedSeriesInstanceUIDs;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* SourceImageSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @param {array[object]} displaysets
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* SourceImageSequence.
|
||||
* @param {object} derivatedDisplaySet.metadata
|
||||
* @param {array[object]} displaysets
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
|
||||
metadata,
|
||||
displaySets
|
||||
) {
|
||||
@@ -1034,9 +1080,7 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
||||
SourceImageSequence = metadata.SourceImageSequence;
|
||||
} else {
|
||||
const { PerFrameFunctionalGroupsSequence } = metadata;
|
||||
const firstFunctionalGroups = _toArray(
|
||||
PerFrameFunctionalGroupsSequence
|
||||
)[0];
|
||||
const firstFunctionalGroups = _toArray(PerFrameFunctionalGroupsSequence)[0];
|
||||
if (firstFunctionalGroups) {
|
||||
const { DerivationImageSequence } = firstFunctionalGroups;
|
||||
SourceImageSequence = DerivationImageSequence;
|
||||
@@ -1056,22 +1100,25 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
||||
displaySets,
|
||||
ReferencedSOPInstanceUID
|
||||
);
|
||||
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
|
||||
if (
|
||||
referencedSeriesInstanceUIDs &&
|
||||
referencedSeriesInstanceUIDs.length !== 0
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return referencedSeriesInstanceUIDs;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* SOPInstanceUID of the displaySets.
|
||||
* @param {array[object]} displaysets
|
||||
* @param {string} SOPInstanceUID
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
|
||||
* Returns the referenced series instance UIDs by searching the information in the
|
||||
* SOPInstanceUID of the displaySets.
|
||||
* @param {array[object]} displaysets
|
||||
* @param {string} SOPInstanceUID
|
||||
* @return {array[string]} referenced series instance UIDs.
|
||||
*/
|
||||
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
|
||||
displaySets,
|
||||
SOPInstanceUID
|
||||
) {
|
||||
@@ -1092,10 +1139,10 @@ function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function _toArray(arrayOrObject) {
|
||||
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
|
||||
}
|
||||
|
||||
export {StudyMetadata};
|
||||
export { StudyMetadata };
|
||||
@@ -20,6 +20,7 @@ import ui from './ui';
|
||||
import user from './user.js';
|
||||
import errorHandler from './errorHandler.js';
|
||||
import utils, { hotkeys } from './utils/';
|
||||
import str2ab from './utils/str2ab';
|
||||
|
||||
import {
|
||||
UINotificationService,
|
||||
@@ -96,6 +97,8 @@ export {
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
LoggerService,
|
||||
|
||||
str2ab,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('Top level exports', () => {
|
||||
'cornerstone',
|
||||
'default', //
|
||||
'errorHandler',
|
||||
'str2ab',
|
||||
'string',
|
||||
'ui',
|
||||
'user',
|
||||
|
||||
@@ -78,17 +78,6 @@ export const setActiveViewportSpecificData = viewportSpecificData => ({
|
||||
/**
|
||||
* NOT-VIEWPORT
|
||||
*/
|
||||
export const setStudyLoadingProgress = (progressId, progressData) => ({
|
||||
type: 'SET_STUDY_LOADING_PROGRESS',
|
||||
progressId,
|
||||
progressData,
|
||||
});
|
||||
|
||||
export const clearStudyLoadingProgress = progressId => ({
|
||||
type: 'CLEAR_STUDY_LOADING_PROGRESS',
|
||||
progressId,
|
||||
});
|
||||
|
||||
export const setUserPreferences = state => ({
|
||||
type: SET_USER_PREFERENCES,
|
||||
state,
|
||||
@@ -134,8 +123,6 @@ const actions = {
|
||||
/**
|
||||
* NOT-VIEWPORT
|
||||
*/
|
||||
setStudyLoadingProgress,
|
||||
clearStudyLoadingProgress,
|
||||
setUserPreferences,
|
||||
setExtensionData,
|
||||
setTimepoints,
|
||||
|
||||
@@ -11,8 +11,6 @@ describe('actions', () => {
|
||||
'setLayout',
|
||||
'clearViewportSpecificData',
|
||||
'setActiveViewportSpecificData',
|
||||
'setStudyLoadingProgress',
|
||||
'clearStudyLoadingProgress',
|
||||
'setUserPreferences',
|
||||
'setExtensionData',
|
||||
'setTimepoints',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import extensions from './extensions';
|
||||
import loading from './loading';
|
||||
import preferences from './preferences';
|
||||
import servers from './servers';
|
||||
import studies from './studies';
|
||||
@@ -8,7 +7,6 @@ import viewports from './viewports';
|
||||
|
||||
const reducers = {
|
||||
extensions,
|
||||
loading,
|
||||
preferences,
|
||||
servers,
|
||||
studies,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
const defaultState = {
|
||||
progress: {},
|
||||
lastUpdated: null,
|
||||
};
|
||||
|
||||
const loading = (state = defaultState, action) => {
|
||||
let progress;
|
||||
let lastUpdated;
|
||||
switch (action.type) {
|
||||
case 'SET_STUDY_LOADING_PROGRESS':
|
||||
progress = cloneDeep(state).progress;
|
||||
progress[action.progressId] = action.progressData;
|
||||
|
||||
// This is a workaround so we can easily identify changes
|
||||
// to the progress object without doing deep comparison.
|
||||
lastUpdated = new Date().getTime();
|
||||
|
||||
return Object.assign({}, state, { progress, lastUpdated });
|
||||
case 'CLEAR_STUDY_LOADING_PROGRESS':
|
||||
progress = cloneDeep(state).progress;
|
||||
delete progress[action.progressId];
|
||||
|
||||
lastUpdated = new Date().getTime();
|
||||
|
||||
return Object.assign({}, state, { progress, lastUpdated });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default loading;
|
||||
@@ -260,7 +260,6 @@ class MeasurementService {
|
||||
const sourceInfo = this._getSourceInfo(source);
|
||||
|
||||
if (!definition) {
|
||||
console.log('TEST');
|
||||
log.warn('No source definition provided. Exiting early.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
|
||||
/**
|
||||
* An implementation of the static wado client, that fetches data from
|
||||
* a static response rather than actually doing real queries. This allows
|
||||
* fast encoding of test data, but because it is static, anything actually
|
||||
* performing searches doesn't work. This version fixes the query issue
|
||||
* by manually implementing a query option.
|
||||
*/
|
||||
export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
static filterKeys = {
|
||||
"StudyInstanceUID": "0020000D",
|
||||
"PatientName": "00100010",
|
||||
"00100020": "mrn",
|
||||
"PatientID": "00100020",
|
||||
"StudyDescription": "00081030",
|
||||
"StudyDate": "00080020",
|
||||
"ModalitiesInStudy": "00080061",
|
||||
AccessionNumber: "00080050",
|
||||
};
|
||||
|
||||
constructor(qidoConfig) {
|
||||
super(qidoConfig);
|
||||
this.staticWado = qidoConfig.staticWado;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the search for studies remote query with a local version which
|
||||
* retrieves a complete query list and then sub-selects from it locally.
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async searchForStudies(options) {
|
||||
if (!this.staticWado) return super.searchForStudies(options);
|
||||
|
||||
let searchResult = await super.searchForStudies(options);
|
||||
const { queryParams } = options;
|
||||
if (!queryParams) return searchResult;
|
||||
const filtered = searchResult.filter(study => {
|
||||
for (const key of Object.keys(StaticWadoClient.filterKeys)) {
|
||||
if (!this.filterItem(key, queryParams, study)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares values, matching any instance of desired to any instance of
|
||||
* actual by recursively go through the paired set of values. That is,
|
||||
* this is O(m*n) where m is how many items in desired and n is the length of actual
|
||||
* Then, at the individual item node, compares the Alphabetic name if present,
|
||||
* and does a sub-string matching on string values, and otherwise does an
|
||||
* exact match comparison.
|
||||
*
|
||||
* @param {*} desired
|
||||
* @param {*} actual
|
||||
* @returns true if the values match
|
||||
*/
|
||||
compareValues(desired, actual) {
|
||||
if (Array.isArray(desired)) {
|
||||
return desired.find(item => this.compareValues(item, actual));
|
||||
}
|
||||
if (Array.isArray(actual)) {
|
||||
return actual.find(actualItem => this.compareValues(desired, actualItem));
|
||||
}
|
||||
if (actual && actual.Alphabetic) {
|
||||
actual = actual.Alphabetic;
|
||||
}
|
||||
if (typeof (actual) == 'string') {
|
||||
if (actual.length === 0) return true;
|
||||
if (desired.length === 0 || desired === '*') return true;
|
||||
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
|
||||
console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`)
|
||||
return actual.indexOf(desired.substring(1, desired.length - 1)) != -1;
|
||||
} else if (desired[desired.length - 1] === '*') {
|
||||
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
|
||||
} else if (desired[0] === '*') {
|
||||
return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1;
|
||||
}
|
||||
}
|
||||
return desired === actual;
|
||||
}
|
||||
|
||||
/** Compares a pair of dates to see if the value is within the range */
|
||||
compareDateRange(range, value) {
|
||||
if (!value) return true;
|
||||
const dash = range.indexOf('-');
|
||||
if (dash === -1) return this.compareValues(range, value);
|
||||
const start = range.substring(0, dash);
|
||||
const end = range.substring(dash + 1);
|
||||
return (!start || value >= start) &&
|
||||
(!end || value <= end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the return list by the query parameters.
|
||||
*
|
||||
* @param {*} key
|
||||
* @param {*} queryParams
|
||||
* @param {*} study
|
||||
* @returns
|
||||
*/
|
||||
filterItem(key, queryParams, study) {
|
||||
const altKey = StaticWadoClient.filterKeys[key] || key;
|
||||
if (!queryParams) return true;
|
||||
const testValue = queryParams[key] || queryParams[altKey];
|
||||
if (!testValue) return true;
|
||||
const valueElem = study[key] || study[altKey];
|
||||
if (!valueElem) return false;
|
||||
if (valueElem.vr == 'DA') return this.compareDateRange(testValue, valueElem.Value[0]);
|
||||
const value = valueElem.Value;
|
||||
return this.compareValues(testValue, value) && true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import StaticWadoClient from './StaticWadoClient';
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
|
||||
import errorHandler from '../../../errorHandler';
|
||||
@@ -115,14 +116,18 @@ function resultDataToStudies(resultData) {
|
||||
}
|
||||
|
||||
export default function Studies(server, filter) {
|
||||
const { staticWado } = server;
|
||||
const config = {
|
||||
...server,
|
||||
url: server.qidoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
requestHooks: [getXHRRetryRequestHook()],
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const dicomWeb = staticWado
|
||||
? new StaticWadoClient(config)
|
||||
: new api.DICOMwebClient(config);
|
||||
server.qidoSupportsIncludeField =
|
||||
server.qidoSupportsIncludeField === undefined
|
||||
? true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import StaticWadoClient from '../qido/StaticWadoClient';
|
||||
import dcmjs from 'dcmjs';
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||
@@ -73,7 +73,8 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
configLoad() {
|
||||
const { server } = this;
|
||||
|
||||
const client = new api.DICOMwebClient({
|
||||
const client = new StaticWadoClient({
|
||||
...server,
|
||||
url: server.qidoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
|
||||
@@ -116,6 +116,11 @@ async function makeSOPInstance(server, study, instance) {
|
||||
};
|
||||
study.seriesMap[SeriesInstanceUID] = series;
|
||||
study.series.push(series);
|
||||
} else {
|
||||
if (series.SeriesDate === undefined)
|
||||
series.SeriesDate = naturalizedInstance.SeriesDate;
|
||||
if (series.SeriesTime === undefined)
|
||||
series.SeriesTime = naturalizedInstance.SeriesTime;
|
||||
}
|
||||
|
||||
const wadouri = buildInstanceWadoUrl(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ReconstructionIssues } from './../enums.js';
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a series is reconstructable to a 3D volume.
|
||||
*
|
||||
@@ -78,7 +77,9 @@ function processSingleframe(instances) {
|
||||
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
|
||||
} else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
|
||||
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
|
||||
} else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
|
||||
} else if (
|
||||
!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)
|
||||
) {
|
||||
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
|
||||
}
|
||||
|
||||
@@ -92,7 +93,10 @@ function processSingleframe(instances) {
|
||||
reconstructionIssues.push(ReconstructionIssues.DATASET_4D);
|
||||
}
|
||||
|
||||
return { value: reconstructionIssues.length === 0 ? true : false, reconstructionIssues };
|
||||
return {
|
||||
value: reconstructionIssues.length === 0 ? true : false,
|
||||
reconstructionIssues,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,14 +120,12 @@ function isSpacingUniform(instances, datasetIs4D) {
|
||||
// If spacing is on a uniform grid but we are missing frames,
|
||||
// Allow reconstruction, but pass back the number of missing frames.
|
||||
if (n > 2) {
|
||||
const lastIpp = instances[n - 1].getData().metadata
|
||||
.ImagePositionPatient;
|
||||
const lastIpp = instances[n - 1].getData().metadata.ImagePositionPatient;
|
||||
|
||||
// We can't reconstruct if we are missing ImagePositionPatient values
|
||||
if (firstImagePositionPatient && lastIpp) {
|
||||
const averageSpacingBetweenFrames =
|
||||
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
|
||||
(n - 1);
|
||||
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (n - 1);
|
||||
|
||||
let previousImagePositionPatient = firstImagePositionPatient;
|
||||
|
||||
@@ -136,7 +138,7 @@ function isSpacingUniform(instances, datasetIs4D) {
|
||||
previousImagePositionPatient
|
||||
);
|
||||
|
||||
if (datasetIs4D && spacingBetweenFrames < 1.e-3) {
|
||||
if (datasetIs4D && spacingBetweenFrames < 1e-3) {
|
||||
// the dataset is 4D, if the distance is zero, means that we are
|
||||
// checking the 4th dimension. Do not return, since we want still to
|
||||
// check the 3rd dimension spacing.
|
||||
@@ -164,10 +166,13 @@ function isSpacingUniform(instances, datasetIs4D) {
|
||||
}
|
||||
}
|
||||
|
||||
return { isUniform: reconstructionIssues.length === 0 ? true : false, missingFrames, reconstructionIssues };
|
||||
return {
|
||||
isUniform: reconstructionIssues.length === 0 ? true : false,
|
||||
missingFrames,
|
||||
reconstructionIssues,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if 4D dataset.
|
||||
*
|
||||
@@ -181,7 +186,7 @@ function isSpacingUniform(instances, datasetIs4D) {
|
||||
*
|
||||
* @returns {boolean} dataset4D value.
|
||||
*/
|
||||
function _isDataset4D(instances) {
|
||||
function _isDataset4D(instances) {
|
||||
const n = instances.length;
|
||||
for (let ii = 0; ii < n; ++ii) {
|
||||
const instanceMetadataControl = instances[ii].getData().metadata;
|
||||
@@ -204,7 +209,12 @@ function isSpacingUniform(instances, datasetIs4D) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
|
||||
if (
|
||||
_isSameArray(
|
||||
instanceMetadataControl.ImagePositionPatient,
|
||||
instanceMetadata.ImagePositionPatient
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -265,11 +275,11 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
||||
function _getPerpendicularDistance(a, b) {
|
||||
return Math.sqrt(
|
||||
Math.pow(a[0] - b[0], 2) +
|
||||
Math.pow(a[1] - b[1], 2) +
|
||||
Math.pow(a[2] - b[2], 2)
|
||||
Math.pow(a[1] - b[1], 2) +
|
||||
Math.pow(a[2] - b[2], 2)
|
||||
);
|
||||
}
|
||||
|
||||
const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
|
||||
|
||||
export {isDisplaySetReconstructable, isSpacingUniform};
|
||||
export { isDisplaySetReconstructable, isSpacingUniform };
|
||||
@@ -3,6 +3,68 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.5](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.4...@ohif/ui@1.10.5) (2021-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.3...@ohif/ui@1.10.4) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.2...@ohif/ui@1.10.3) (2021-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.1...@ohif/ui@1.10.2) (2021-09-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* throttle thumbnail progress updates ([#2557](https://github.com/OHIF/Viewers/issues/2557)) ([66c587b](https://github.com/OHIF/Viewers/commit/66c587b139835201331a64bc24a17901e7583649))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.10.0...@ohif/ui@1.10.1) (2021-09-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.3...@ohif/ui@1.10.0) (2021-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.2...@ohif/ui@1.9.3) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.1...@ohif/ui@1.9.2) (2021-05-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "1.9.2",
|
||||
"version": "1.10.5",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,6 +4,29 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.RoundedButtonGroup .badgeNumber-container {
|
||||
background-color: var(--ui-border-color-dark);
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
bottom: 10px;
|
||||
left: 5px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.RoundedButtonGroup .badgeNumber {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.roundedButtonWrapper {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -36,7 +59,12 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
|
||||
.roundedButtonWrapper
|
||||
.roundedButton
|
||||
svg
|
||||
.roundedButtonWrapper
|
||||
.roundedButton
|
||||
span {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class RoundedButtonGroup extends Component {
|
||||
PropTypes.shape({
|
||||
value: PropTypes.any,
|
||||
label: PropTypes.string,
|
||||
stateEvent: PropTypes.string,
|
||||
icon: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
@@ -30,6 +31,15 @@ class RoundedButtonGroup extends Component {
|
||||
value: null,
|
||||
};
|
||||
|
||||
state = {
|
||||
badgeNumbers: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onStateEvent = this.onStateEvent.bind(this);
|
||||
}
|
||||
|
||||
onClickOption = value => {
|
||||
let newValue = value;
|
||||
if (this.props.value === value) {
|
||||
@@ -41,6 +51,48 @@ class RoundedButtonGroup extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onStateEvent(event) {
|
||||
const optionIndex = this.props.options.findIndex(
|
||||
o => o.value === event.detail.target
|
||||
);
|
||||
if (optionIndex > -1) {
|
||||
const badgeNumbers = this.state.badgeNumbers;
|
||||
badgeNumbers[optionIndex] = event.detail.badgeNumber;
|
||||
this.setState({ badgeNumbers });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.options.forEach(option => {
|
||||
if (option.stateEvent) {
|
||||
document.addEventListener(option.stateEvent, this.onStateEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
this.props.options.forEach((option, index) => {
|
||||
if (
|
||||
option.stateEvent &&
|
||||
option.stateEvent !==
|
||||
(prevProps.options[index]
|
||||
? prevProps.options[index].stateEvent
|
||||
: null)
|
||||
) {
|
||||
document.removeEventListener(option.stateEvent, this.onStateEvent);
|
||||
document.addEventListener(option.stateEvent, this.onStateEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.options.forEach(option => {
|
||||
if (option.stateEvent) {
|
||||
document.removeEventListener(option.stateEvent, this.onStateEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = classnames(
|
||||
RoundedButtonGroup.className,
|
||||
@@ -62,6 +114,14 @@ class RoundedButtonGroup extends Component {
|
||||
<div className="bottomLabel">{option.bottomLabel}</div>
|
||||
);
|
||||
|
||||
let badgeNumber = this.state.badgeNumbers[index];
|
||||
const badgeNumberOverflow = String(badgeNumber).length > 2;
|
||||
badgeNumber = badgeNumber
|
||||
? badgeNumberOverflow
|
||||
? 99
|
||||
: badgeNumber
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
@@ -70,6 +130,14 @@ class RoundedButtonGroup extends Component {
|
||||
>
|
||||
<div className="roundedButton">
|
||||
{optionText}
|
||||
{badgeNumber && (
|
||||
<div className="badgeNumber-container">
|
||||
<span className="badgeNumber">
|
||||
{badgeNumber}
|
||||
{badgeNumberOverflow && '+'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{iconProps && <Icon {...iconProps} />}
|
||||
</div>
|
||||
{bottomLabel}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import './ImageThumbnail.styl';
|
||||
|
||||
import { utils } from '@ohif/core';
|
||||
import React, { useState, useEffect, createRef } from 'react';
|
||||
import React, { useState, useEffect, createRef, useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -23,6 +23,7 @@ function ImageThumbnail(props) {
|
||||
imageId,
|
||||
stackPercentComplete,
|
||||
error: propsError,
|
||||
showProgressBar,
|
||||
} = props;
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
@@ -39,7 +40,8 @@ function ImageThumbnail(props) {
|
||||
loadingOrError = <ViewportLoadingIndicator />;
|
||||
}
|
||||
|
||||
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
|
||||
const showStackLoadingProgressBar =
|
||||
showProgressBar && stackPercentComplete !== undefined;
|
||||
|
||||
const shouldRenderToCanvas = () => {
|
||||
return imageId && !imageSrc;
|
||||
@@ -71,11 +73,11 @@ function ImageThumbnail(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const purgeCancelablePromise = () => {
|
||||
const purgeCancelablePromise = useCallback(() => {
|
||||
if (cancelablePromise) {
|
||||
cancelablePromise.cancel();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -96,7 +98,13 @@ function ImageThumbnail(props) {
|
||||
setImagePromise();
|
||||
fetchImagePromise();
|
||||
}
|
||||
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
|
||||
}, [
|
||||
fetchImagePromise,
|
||||
image.imageId,
|
||||
imageId,
|
||||
purgeCancelablePromise,
|
||||
setImagePromise,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={classNames('ImageThumbnail', { active: active })}>
|
||||
@@ -104,14 +112,14 @@ function ImageThumbnail(props) {
|
||||
{shouldRenderToCanvas() ? (
|
||||
<canvas ref={canvasRef} width={width} height={height} />
|
||||
) : (
|
||||
<img
|
||||
className="static-image"
|
||||
src={imageSrc}
|
||||
//width={this.props.width}
|
||||
height={height}
|
||||
alt={''}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className="static-image"
|
||||
src={imageSrc}
|
||||
//width={this.props.width}
|
||||
height={height}
|
||||
alt={''}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{loadingOrError}
|
||||
{showStackLoadingProgressBar && (
|
||||
@@ -135,6 +143,7 @@ ImageThumbnail.propTypes = {
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
stackPercentComplete: PropTypes.number.isRequired,
|
||||
showProgressBar: PropTypes.bool,
|
||||
};
|
||||
|
||||
ImageThumbnail.defaultProps = {
|
||||
@@ -143,6 +152,7 @@ ImageThumbnail.defaultProps = {
|
||||
stackPercentComplete: 0,
|
||||
width: 217,
|
||||
height: 123,
|
||||
showProgressBar: true,
|
||||
};
|
||||
|
||||
export default ImageThumbnail;
|
||||
@@ -9,6 +9,7 @@ function StudyBrowser(props) {
|
||||
onThumbnailClick,
|
||||
onThumbnailDoubleClick,
|
||||
supportsDrag,
|
||||
showThumbnailProgressBar,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -24,12 +25,12 @@ function StudyBrowser(props) {
|
||||
altImageText,
|
||||
displaySetInstanceUID,
|
||||
imageId,
|
||||
InstanceNumber,
|
||||
derivedDisplaySetsNumber,
|
||||
numImageFrames,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
stackPercentComplete,
|
||||
hasWarnings,
|
||||
hasDerivedDisplaySets,
|
||||
} = thumb;
|
||||
|
||||
return (
|
||||
@@ -48,19 +49,20 @@ function StudyBrowser(props) {
|
||||
// Thumb
|
||||
altImageText={altImageText}
|
||||
imageId={imageId}
|
||||
InstanceNumber={InstanceNumber}
|
||||
derivedDisplaySetsNumber={derivedDisplaySetsNumber}
|
||||
displaySetInstanceUID={displaySetInstanceUID} // used by drop
|
||||
numImageFrames={numImageFrames}
|
||||
SeriesDescription={SeriesDescription}
|
||||
SeriesNumber={SeriesNumber}
|
||||
hasWarnings={hasWarnings}
|
||||
stackPercentComplete={stackPercentComplete}
|
||||
hasDerivedDisplaySets={hasDerivedDisplaySets}
|
||||
// Events
|
||||
onClick={onThumbnailClick.bind(
|
||||
undefined,
|
||||
displaySetInstanceUID
|
||||
)}
|
||||
onDoubleClick={onThumbnailDoubleClick}
|
||||
showProgressBar={showThumbnailProgressBar}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -83,7 +85,7 @@ StudyBrowser.propTypes = {
|
||||
altImageText: PropTypes.string,
|
||||
displaySetInstanceUID: PropTypes.string.isRequired,
|
||||
imageId: PropTypes.string,
|
||||
InstanceNumber: PropTypes.number,
|
||||
derivedDisplaySetsNumber: PropTypes.number,
|
||||
numImageFrames: PropTypes.number,
|
||||
SeriesDescription: PropTypes.string,
|
||||
SeriesNumber: PropTypes.number,
|
||||
@@ -95,6 +97,7 @@ StudyBrowser.propTypes = {
|
||||
supportsDrag: PropTypes.bool,
|
||||
onThumbnailClick: PropTypes.func,
|
||||
onThumbnailDoubleClick: PropTypes.func,
|
||||
showThumbnailProgressBar: PropTypes.bool,
|
||||
};
|
||||
|
||||
StudyBrowser.defaultProps = {
|
||||
@@ -102,6 +105,7 @@ StudyBrowser.defaultProps = {
|
||||
supportsDrag: true,
|
||||
onThumbnailClick: noop,
|
||||
onThumbnailDoubleClick: noop,
|
||||
showThumbnailProgressBar: true,
|
||||
};
|
||||
|
||||
export { StudyBrowser };
|
||||
@@ -1,34 +1,43 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import throttle from 'lodash.throttle';
|
||||
import { useDrag } from 'react-dnd';
|
||||
import { classes } from '@ohif/core';
|
||||
import ImageThumbnail from './ImageThumbnail';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from './../../elements/Icon';
|
||||
import { Tooltip } from './../tooltip';
|
||||
import { OverlayTrigger } from './../overlayTrigger';
|
||||
|
||||
import './Thumbnail.styl';
|
||||
|
||||
const StudyLoadingListener = classes.StudyLoadingListener;
|
||||
|
||||
function ThumbnailFooter({
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
hasWarnings
|
||||
hasWarnings,
|
||||
hasDerivedDisplaySets,
|
||||
}) {
|
||||
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
|
||||
const [derivedDisplaySetsActive, derivedDisplaySetsActiveSet] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
let unmounted = false
|
||||
let unmounted = false;
|
||||
hasWarnings.then(response => {
|
||||
if (!unmounted) {
|
||||
inconsistencyWarningsSet(response)
|
||||
inconsistencyWarningsSet(response);
|
||||
}
|
||||
})
|
||||
});
|
||||
hasDerivedDisplaySets.then(response => {
|
||||
if (!unmounted) {
|
||||
derivedDisplaySetsActiveSet(response);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
unmounted = true
|
||||
}
|
||||
}, [])
|
||||
unmounted = true;
|
||||
};
|
||||
}, [hasWarnings, hasDerivedDisplaySets]);
|
||||
|
||||
const infoOnly = !SeriesDescription;
|
||||
|
||||
@@ -41,7 +50,7 @@ function ThumbnailFooter({
|
||||
);
|
||||
};
|
||||
|
||||
const getWarningContent = (inconsistencyWarnings) => {
|
||||
const getWarningContent = inconsistencyWarnings => {
|
||||
if (Array.isArray(inconsistencyWarnings)) {
|
||||
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
|
||||
return <li key={index}>{warn}</li>;
|
||||
@@ -54,8 +63,8 @@ function ThumbnailFooter({
|
||||
};
|
||||
|
||||
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
|
||||
return(
|
||||
<React.Fragment>
|
||||
return (
|
||||
<React.Fragment>
|
||||
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
|
||||
<OverlayTrigger
|
||||
key={SeriesNumber}
|
||||
@@ -67,7 +76,9 @@ function ThumbnailFooter({
|
||||
id="tooltip-left"
|
||||
>
|
||||
<div className="warningTitle">Series Inconsistencies</div>
|
||||
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div>
|
||||
<div className="warningContent">
|
||||
{getWarningContent(inconsistencyWarnings)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
@@ -79,34 +90,67 @@ function ThumbnailFooter({
|
||||
</OverlayTrigger>
|
||||
) : (
|
||||
<React.Fragment></React.Fragment>
|
||||
)}
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const getDerivedInfo = derivedDisplaySetsActive => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{derivedDisplaySetsActive ? (
|
||||
<div className="derived">
|
||||
<Icon name="link" />
|
||||
</div>
|
||||
) : (
|
||||
<React.Fragment></React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getSeriesInformation = (
|
||||
SeriesNumber,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
inconsistencyWarnings
|
||||
inconsistencyWarnings,
|
||||
derivedDisplaySetsActive
|
||||
) => {
|
||||
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
|
||||
if (!SeriesNumber && !numImageFrames) {
|
||||
return;
|
||||
}
|
||||
const seriesInformation =
|
||||
const seriesInformation = (
|
||||
<div className="series-information">
|
||||
{getInfo(SeriesNumber, 'S:')}
|
||||
{getInfo(InstanceNumber, 'I:')}
|
||||
{getInfo(numImageFrames, '', 'image-frames')}
|
||||
<React.Fragment>
|
||||
{SeriesNumber !== undefined ? (
|
||||
getInfo(SeriesNumber, 'S:')
|
||||
) : (
|
||||
<React.Fragment></React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
{numImageFrames !== undefined ? (
|
||||
getInfo(numImageFrames, '', 'image-frames')
|
||||
) : (
|
||||
<React.Fragment></React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
{getDerivedInfo(derivedDisplaySetsActive)}
|
||||
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (seriesInformation);
|
||||
return seriesInformation;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('series-details', { 'info-only': infoOnly })}>
|
||||
<div className="series-description">{SeriesDescription}</div>
|
||||
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)}
|
||||
{getSeriesInformation(
|
||||
SeriesNumber,
|
||||
numImageFrames,
|
||||
inconsistencyWarnings,
|
||||
derivedDisplaySetsActive
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -119,19 +163,37 @@ function Thumbnail(props) {
|
||||
displaySetInstanceUID,
|
||||
imageId,
|
||||
imageSrc,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
hasWarnings,
|
||||
stackPercentComplete,
|
||||
StudyInstanceUID,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
onMouseDown,
|
||||
supportsDrag,
|
||||
showProgressBar,
|
||||
} = props;
|
||||
|
||||
const [stackPercentComplete, setStackPercentComplete] = useState(0);
|
||||
useEffect(() => {
|
||||
const onProgressChange = throttle(({ detail }) => {
|
||||
const { progressId, progressData } = detail;
|
||||
if (`StackProgress:${displaySetInstanceUID}` === progressId) {
|
||||
const percent = progressData ? progressData.percentComplete : 0;
|
||||
setStackPercentComplete(percent);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
document.addEventListener(
|
||||
StudyLoadingListener.events.OnProgress,
|
||||
onProgressChange
|
||||
);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
StudyLoadingListener.events.OnProgress,
|
||||
onProgressChange
|
||||
);
|
||||
};
|
||||
}, [displaySetInstanceUID]);
|
||||
|
||||
const [collectedProps, drag, dragPreview] = useDrag({
|
||||
// `droppedItem` in `dropTarget`
|
||||
// The only data it will have access to
|
||||
@@ -164,6 +226,7 @@ function Thumbnail(props) {
|
||||
imageId={imageId}
|
||||
error={error}
|
||||
stackPercentComplete={stackPercentComplete}
|
||||
showProgressBar={showProgressBar}
|
||||
/>
|
||||
)}
|
||||
{/* SHOW TEXT ALTERNATIVE */}
|
||||
@@ -197,12 +260,13 @@ Thumbnail.propTypes = {
|
||||
altImageText: PropTypes.string,
|
||||
SeriesDescription: PropTypes.string,
|
||||
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
hasWarnings: PropTypes.instanceOf(Promise),
|
||||
hasDerivedDisplaySets: PropTypes.instanceOf(Promise),
|
||||
numImageFrames: PropTypes.number,
|
||||
onDoubleClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onMouseDown: PropTypes.func,
|
||||
showProgressBar: PropTypes.bool,
|
||||
};
|
||||
|
||||
Thumbnail.defaultProps = {
|
||||
|
||||
@@ -6,10 +6,13 @@ import React, {
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { classes } from '@ohif/core';
|
||||
|
||||
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
||||
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
||||
|
||||
const { LogManager } = classes;
|
||||
|
||||
const SnackbarContext = createContext(null);
|
||||
|
||||
export const useSnackbarContext = () => useContext(SnackbarContext);
|
||||
@@ -28,6 +31,20 @@ const SnackbarProvider = ({ children, service }) => {
|
||||
const [count, setCount] = useState(1);
|
||||
const [snackbarItems, setSnackbarItems] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const onLogHandler = ({ type, notify, title, message }) => {
|
||||
if (notify) {
|
||||
show({ type, title, message });
|
||||
}
|
||||
};
|
||||
|
||||
LogManager.subscribe(LogManager.EVENTS.OnLog, onLogHandler);
|
||||
|
||||
return () => {
|
||||
LogManager.unsubscribe(LogManager.EVENTS.OnLog, onLogHandler);
|
||||
};
|
||||
}, [show]);
|
||||
|
||||
const show = useCallback(
|
||||
options => {
|
||||
if (!options || (!options.title && !options.message)) {
|
||||
|
||||
@@ -10,9 +10,9 @@ services:
|
||||
ohif_viewer:
|
||||
build:
|
||||
# Project root
|
||||
context: ./../../
|
||||
context: ./../../../../
|
||||
# Relative to context
|
||||
dockerfile: ./docker/OpenResty-Orthanc-Keycloak/dockerfile
|
||||
dockerfile: ./platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile
|
||||
image: webapp:latest
|
||||
container_name: webapp
|
||||
volumes:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
# TODO: Update to use Postgres
|
||||
# https://github.com/mrts/docker-postgresql-multiple-databases
|
||||
orthanc:
|
||||
image: jodogne/orthanc-plugins:1.5.6
|
||||
image: jodogne/orthanc-plugins:1.9.7
|
||||
hostname: orthanc
|
||||
container_name: orthanc
|
||||
volumes:
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
|
||||
# LINK: https://hub.docker.com/r/jboss/keycloak
|
||||
keycloak:
|
||||
image: jboss/keycloak:6.0.1
|
||||
image: jboss/keycloak:16.1.0
|
||||
hostname: keycloak
|
||||
container_name: keycloak
|
||||
volumes:
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
|
||||
# Stage 1: Build the application
|
||||
FROM node:11.2.0-slim as builder
|
||||
|
||||
# Needs to be pinned at 16.x for now because of https://github.com/webpack/webpack/issues/14532
|
||||
FROM node:lts-slim as builder
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
@@ -36,12 +38,12 @@ COPY yarn.lock /usr/src/app/yarn.lock
|
||||
|
||||
ADD . /usr/src/app/
|
||||
RUN yarn install
|
||||
RUN yarn run build:web
|
||||
RUN yarn run build
|
||||
|
||||
# Stage 2: Bundle the built application into a Docker container
|
||||
# which runs openresty (nginx) using Alpine Linux
|
||||
# LINK: https://hub.docker.com/r/openresty/openresty
|
||||
FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat
|
||||
FROM openresty/openresty:1.19.9.1-4-alpine-fat
|
||||
|
||||
RUN mkdir /var/log/nginx
|
||||
RUN apk add --no-cache openssl
|
||||
@@ -60,6 +62,6 @@ RUN luarocks install lua-resty-openidc
|
||||
RUN luarocks install luacrypto
|
||||
|
||||
# Copy build output to image
|
||||
COPY --from=builder /usr/src/app/build /var/www/html
|
||||
COPY --from=builder /usr/src/app/platform/viewer/dist /var/www/html
|
||||
|
||||
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]
|
||||
@@ -10,9 +10,9 @@ services:
|
||||
ohif_viewer:
|
||||
build:
|
||||
# Project root
|
||||
context: ./../../
|
||||
context: ./../../../../
|
||||
# Relative to context
|
||||
dockerfile: ./docker/OpenResty-Orthanc/dockerfile
|
||||
dockerfile: ./platform/viewer/.recipes/OpenResty-Orthanc/dockerfile
|
||||
image: webapp:latest
|
||||
container_name: webapp
|
||||
volumes:
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
# TODO: Update to use Postgres
|
||||
# https://github.com/mrts/docker-postgresql-multiple-databases
|
||||
orthanc:
|
||||
image: jodogne/orthanc-plugins:1.5.6
|
||||
image: jodogne/orthanc-plugins:1.9.7
|
||||
hostname: orthanc
|
||||
container_name: orthanc
|
||||
volumes:
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
|
||||
# Stage 1: Build the application
|
||||
FROM node:11.2.0-slim as builder
|
||||
|
||||
# Needs to be pinned at 16.x for now because of https://github.com/webpack/webpack/issues/14532
|
||||
FROM node:lts-slim as builder
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
@@ -36,12 +38,12 @@ COPY yarn.lock /usr/src/app/yarn.lock
|
||||
|
||||
ADD . /usr/src/app/
|
||||
RUN yarn install
|
||||
RUN yarn run build:web
|
||||
RUN yarn run build
|
||||
|
||||
# Stage 2: Bundle the built application into a Docker container
|
||||
# which runs openresty (nginx) using Alpine Linux
|
||||
# LINK: https://hub.docker.com/r/openresty/openresty
|
||||
FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat
|
||||
FROM openresty/openresty:1.19.9.1-4-alpine-fat
|
||||
|
||||
RUN mkdir /var/log/nginx
|
||||
RUN apk add --no-cache openssl
|
||||
@@ -60,6 +62,6 @@ RUN luarocks install lua-resty-openidc
|
||||
RUN luarocks install luacrypto
|
||||
|
||||
# Copy build output to image
|
||||
COPY --from=builder /usr/src/app/build /var/www/html
|
||||
COPY --from=builder /usr/src/app/platform/viewer/dist /var/www/html
|
||||
|
||||
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]
|
||||
@@ -12,8 +12,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { InjectManifest } = require('workbox-webpack-plugin');
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
// ~~ Rules
|
||||
const extractStyleChunksRule = require('./rules/extractStyleChunks.js');
|
||||
// ~~ Directories
|
||||
@@ -28,6 +26,16 @@ const PROXY_TARGET = process.env.PROXY_TARGET;
|
||||
const PROXY_DOMAIN = process.env.PROXY_DOMAIN;
|
||||
const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
|
||||
|
||||
|
||||
const setHeaders = (res, path) => {
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
if (path.indexOf('.gz') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'gzip')
|
||||
} else if (path.indexOf('.br') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'br')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR });
|
||||
const isProdBuild = process.env.NODE_ENV === 'production';
|
||||
@@ -49,6 +57,16 @@ module.exports = (env, argv) => {
|
||||
}
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
// We use this alias and the CopyPlugin below to support using the dynamic-import version
|
||||
// of WADO Image Loader, but only when building a PWA. When we build a package, we must use the
|
||||
// bundled version of WADO Image Loader so we can produce a single file for the viewer.
|
||||
// (Note: script-tag version of the viewer will no longer be supported in OHIF v3)
|
||||
alias: {
|
||||
'cornerstone-wado-image-loader':
|
||||
'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js',
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [...extractStyleChunksRule(isProdBuild)],
|
||||
},
|
||||
@@ -78,6 +96,11 @@ module.exports = (env, argv) => {
|
||||
from: `${PUBLIC_DIR}/${APP_CONFIG}`,
|
||||
to: `${DIST_DIR}/app-config.js`,
|
||||
},
|
||||
{
|
||||
from:
|
||||
'../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
|
||||
to: DIST_DIR,
|
||||
},
|
||||
]),
|
||||
// https://github.com/faceyspacey/extract-css-chunks-webpack-plugin#webpack-4-standalone-installation
|
||||
new ExtractCssChunksPlugin({
|
||||
@@ -102,6 +125,15 @@ module.exports = (env, argv) => {
|
||||
// maximumFileSizeToCacheInBytes: 4 * 1024 * 1024
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
// include all types of chunks
|
||||
chunks: 'all',
|
||||
},
|
||||
//runtimeChunk: 'single',
|
||||
minimize: isProdBuild,
|
||||
sideEffects: true,
|
||||
},
|
||||
// https://webpack.js.org/configuration/dev-server/
|
||||
devServer: {
|
||||
// gzip compression of everything served
|
||||
@@ -112,11 +144,40 @@ module.exports = (env, argv) => {
|
||||
hot: true,
|
||||
open: true,
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
public: 'http://localhost:' + 3000,
|
||||
client: {
|
||||
overlay: { errors: true, warnings: false },
|
||||
},
|
||||
'static': [
|
||||
{
|
||||
directory: path.join(require('os').homedir(), 'dicomweb'),
|
||||
staticOptions: {
|
||||
extensions: ['gz', 'br'],
|
||||
index: "index.json.gz",
|
||||
redirect: true,
|
||||
setHeaders,
|
||||
},
|
||||
publicPath: '/dicomweb',
|
||||
},
|
||||
{
|
||||
directory: '../../testdata',
|
||||
staticOptions: {
|
||||
extensions: ['gz', 'br'],
|
||||
index: "index.json.gz",
|
||||
redirect: true,
|
||||
setHeaders,
|
||||
},
|
||||
publicPath: '/testdata',
|
||||
},
|
||||
],
|
||||
//public: 'http://localhost:' + 3000,
|
||||
//writeToDisk: true,
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
},
|
||||
headers: {
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,191 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.12.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.14...@ohif/viewer@4.12.15) (2022-02-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.13...@ohif/viewer@4.12.14) (2022-01-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.12...@ohif/viewer@4.12.13) (2022-01-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.11...@ohif/viewer@4.12.12) (2022-01-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.10...@ohif/viewer@4.12.11) (2022-01-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.9...@ohif/viewer@4.12.10) (2022-01-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.8...@ohif/viewer@4.12.9) (2021-12-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.7...@ohif/viewer@4.12.8) (2021-12-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.6...@ohif/viewer@4.12.7) (2021-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.5...@ohif/viewer@4.12.6) (2021-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.4...@ohif/viewer@4.12.5) (2021-10-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.3...@ohif/viewer@4.12.4) (2021-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.2...@ohif/viewer@4.12.3) (2021-10-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.1...@ohif/viewer@4.12.2) (2021-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.12.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.12.0...@ohif/viewer@4.12.1) (2021-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.12.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.2...@ohif/viewer@4.12.0) (2021-10-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Added support for WADO servers built with static hosting (e.g. S3) ([#2566](https://github.com/OHIF/Viewers/issues/2566)) ([a046c6c](https://github.com/OHIF/Viewers/commit/a046c6c9017a84e5ead71d5113b5e892333b4130))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.11.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.1...@ohif/viewer@4.11.2) (2021-09-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.11.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.11.0...@ohif/viewer@4.11.1) (2021-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.11.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.10.1...@ohif/viewer@4.11.0) (2021-09-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update to WADO Image Loader 4.0.3 to add dynamically imported WebAssembly codec support 🚀 ([#2537](https://github.com/OHIF/Viewers/issues/2537)) ([489d4b0](https://github.com/OHIF/Viewers/commit/489d4b0584f4657ceda1c5b9b68d5e9733827e47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.10.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.10.0...@ohif/viewer@4.10.1) (2021-09-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.10.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.24...@ohif/viewer@4.10.0) (2021-09-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* OHIF-1000: Study prefetcher ([#2466](https://github.com/OHIF/Viewers/issues/2466)) ([8f26045](https://github.com/OHIF/Viewers/commit/8f2604509d24092b721f1333289d838820d68cac))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.9.24](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.23...@ohif/viewer@4.9.24) (2021-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.9.23](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.22...@ohif/viewer@4.9.23) (2021-08-30)
|
||||
|
||||
|
||||
|
||||
@@ -333,6 +333,7 @@ describe('OHIF User Preferences', () => {
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
|
||||
// Header should be in "English (USA)""
|
||||
cy.get('.research-use')
|
||||
.scrollIntoView()
|
||||
@@ -560,6 +561,13 @@ describe('OHIF User Preferences', () => {
|
||||
.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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Open User Preferences modal again
|
||||
cy.openPreferences();
|
||||
|
||||
@@ -668,6 +676,13 @@ describe('OHIF User Preferences', () => {
|
||||
|
||||
// Save changes
|
||||
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').click({ force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Open User Preferences modal
|
||||
cy.openPreferences();
|
||||
// Navigate to Window Level tab
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('OHIF Save Measurements', function() {
|
||||
// cy.get('.sb-closeIcon').click();
|
||||
// });
|
||||
|
||||
it('checks if warning message is displayed on measurements of unsupported tools', function() {
|
||||
/*it('checks if warning message is displayed on measurements of unsupported tools', function() {
|
||||
// Add measurement for unsupported tool in the viewport
|
||||
cy.addAngleMeasurement();
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('OHIF Save Measurements', function() {
|
||||
|
||||
// Close Measurements panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
});
|
||||
});*/
|
||||
|
||||
/*it('checks if measurements of unsupported tools were not saved', function() {
|
||||
// Add measurement for supported tool in the viewport
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '@percy/cypress';
|
||||
import { enable } from 'cornerstone-core';
|
||||
import 'cypress-file-upload';
|
||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||
import {
|
||||
@@ -199,6 +200,11 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
|
||||
.its('cornerstone')
|
||||
.then({ timeout }, $cornerstone => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
const onEnabled = enabledEvt => {
|
||||
const element = enabledEvt.detail.element;
|
||||
|
||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||
};
|
||||
const onEvent = renderedEvt => {
|
||||
const element = renderedEvt.detail.element;
|
||||
|
||||
@@ -209,15 +215,17 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
const onEnabled = enabledEvt => {
|
||||
const element = enabledEvt.detail.element;
|
||||
|
||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||
};
|
||||
$cornerstone.events.addEventListener(
|
||||
'cornerstoneelementenabled',
|
||||
onEnabled
|
||||
);
|
||||
const enabledElements = $cornerstone.getEnabledElements();
|
||||
if (enabledElements && enabledElements.length && !enabledElements[0].invalid) {
|
||||
// Sometimes the page finishes rendering before this gets run,
|
||||
// if so, just resolve immediately.
|
||||
resolve();
|
||||
} else {
|
||||
$cornerstone.events.addEventListener(
|
||||
'cornerstoneelementenabled',
|
||||
onEnabled
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -504,6 +512,9 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => {
|
||||
cy.get('@restoreBtn').click();
|
||||
});
|
||||
|
||||
// Click on Save Button
|
||||
cy.get('@saveBtn').click();
|
||||
|
||||
// Close Success Message overlay (if displayed)
|
||||
cy.get('body').then(body => {
|
||||
if (body.find('.sb-closeIcon').length > 0) {
|
||||
@@ -511,8 +522,6 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => {
|
||||
.first()
|
||||
.click({ force: true });
|
||||
}
|
||||
// Click on Save Button
|
||||
cy.get('@saveBtn').click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -525,6 +534,9 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => {
|
||||
cy.get('@restoreBtn').click();
|
||||
});
|
||||
|
||||
// Click on Save Button
|
||||
cy.get('@saveBtn').click();
|
||||
|
||||
// Close Success Message overlay (if displayed)
|
||||
cy.get('body').then(body => {
|
||||
if (body.find('.sb-closeIcon').length > 0) {
|
||||
@@ -532,8 +544,6 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => {
|
||||
.first()
|
||||
.click({ force: true });
|
||||
}
|
||||
// Click on Save Button
|
||||
cy.get('@saveBtn').click();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "4.9.23",
|
||||
"version": "4.12.15",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -19,21 +19,19 @@
|
||||
"scripts": {
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
|
||||
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js",
|
||||
"dev:viewer": "yarn run dev",
|
||||
"dev:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
"test:e2e:script-tag": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'",
|
||||
"test:e2e:local": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'",
|
||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||
"test:e2e:serve": "cross-env APP_CONFIG=config/e2e.js yarn start",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage",
|
||||
"prepublishOnly": "yarn run build:package",
|
||||
@@ -49,28 +47,28 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.13.14",
|
||||
"@ohif/extension-cornerstone": "^2.10.10",
|
||||
"@ohif/extension-debugging": "^0.2.2",
|
||||
"@ohif/extension-dicom-html": "^1.3.10",
|
||||
"@ohif/core": "^2.16.9",
|
||||
"@ohif/extension-cornerstone": "^2.12.5",
|
||||
"@ohif/extension-debugging": "^0.3.1",
|
||||
"@ohif/extension-dicom-html": "^1.3.16",
|
||||
"@ohif/extension-dicom-microscopy": "^0.52.0",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.6",
|
||||
"@ohif/extension-dicom-rt": "^0.6.13",
|
||||
"@ohif/extension-dicom-segmentation": "^0.6.10",
|
||||
"@ohif/extension-dicom-pdf": "^1.1.2",
|
||||
"@ohif/extension-dicom-rt": "^0.7.7",
|
||||
"@ohif/extension-dicom-segmentation": "^0.7.7",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.1",
|
||||
"@ohif/extension-vtk": "^1.11.15",
|
||||
"@ohif/extension-vtk": "^1.12.13",
|
||||
"@ohif/i18n": "^0.53.0",
|
||||
"@ohif/ui": "^1.9.2",
|
||||
"@ohif/ui": "^1.10.5",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.18.8",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.8.1",
|
||||
"cornerstone-core": "^2.6.1",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"cornerstone-tools": "^6.0.6",
|
||||
"cornerstone-wado-image-loader": "^4.1.0",
|
||||
"dcmjs": "0.19.3",
|
||||
"dicom-parser": "^1.8.11",
|
||||
"dicomweb-client": "^0.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
|
||||
@@ -5,6 +5,16 @@ window.config = {
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
disableServersCache: false,
|
||||
studyPrefetcher: {
|
||||
enabled: true,
|
||||
order: 'closest',
|
||||
displaySetCount: 3,
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
maxNumPrefetchRequests: 100,
|
||||
displayProgress: true,
|
||||
includeActiveDisplaySet: true,
|
||||
},
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'LocalStatic',
|
||||
wadoUriRoot: '/testdata',
|
||||
qidoRoot: '/testdata',
|
||||
wadoRoot: '/testdata',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
staticWado: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Extensions should be able to suggest default values for these?
|
||||
// Or we can require that these be explicitly set
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'LocalStatic',
|
||||
wadoUriRoot: '/dicomweb',
|
||||
qidoRoot: '/dicomweb',
|
||||
wadoRoot: '/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'thumbnail',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
staticWado: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,15 @@
|
||||
window.config = {
|
||||
routerBasename: '/pwa',
|
||||
showStudyList: true,
|
||||
studyPrefetcher: {
|
||||
enabled: true,
|
||||
order: 'closest',
|
||||
displaySetCount: 3,
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
displayProgress: true,
|
||||
includeActiveDisplaySet: true,
|
||||
},
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
|
||||
@@ -1,42 +1,23 @@
|
||||
import { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import OHIF from "@ohif/core";
|
||||
import { useEffect } from 'react';
|
||||
import OHIF from '@ohif/core';
|
||||
|
||||
class StudyLoadingMonitor extends Component {
|
||||
static propTypes = {
|
||||
studies: PropTypes.array.isRequired,
|
||||
setStudyLoadingProgress: PropTypes.func.isRequired,
|
||||
clearStudyLoadingProgress: PropTypes.func.isRequired
|
||||
};
|
||||
const { StudyLoadingListener } = OHIF.classes;
|
||||
|
||||
componentDidMount() {
|
||||
// TODO: This is pretty ugly. The thing is that the StudyLoadingListener
|
||||
// needs to update the Redux store, but shouldn't know that it exists.
|
||||
// I am therefore passing in some functions to update the store instead,
|
||||
// but this should definitely be cleaned up somehow.
|
||||
const options = {
|
||||
_setProgressData: (progressId, progressData) => {
|
||||
this.props.setStudyLoadingProgress(progressId, progressData);
|
||||
},
|
||||
_clearProgressById: progressId => {
|
||||
this.props.clearStudyLoadingProgress(progressId);
|
||||
}
|
||||
const StudyLoadingMonitor = ({ studies }) => {
|
||||
useEffect(() => {
|
||||
const studyLoadingListener = StudyLoadingListener.getInstance();
|
||||
|
||||
if (studies && studies.length > 0) {
|
||||
studyLoadingListener.clear();
|
||||
studyLoadingListener.addStudies(studies);
|
||||
}
|
||||
|
||||
return () => {
|
||||
studyLoadingListener.clear();
|
||||
};
|
||||
}, [studies]);
|
||||
|
||||
const { StudyLoadingListener } = OHIF.classes;
|
||||
this.studyLoadingListener = StudyLoadingListener.getInstance(options);
|
||||
this.studyLoadingListener.clear();
|
||||
this.studyLoadingListener.addStudies(this.props.studies);
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Destroy stack loading listeners when we close the viewer
|
||||
this.studyLoadingListener.clear();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default StudyLoadingMonitor;
|
||||
@@ -0,0 +1,15 @@
|
||||
.StudyPrefetcher {
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: var(--default-color);
|
||||
}
|
||||
|
||||
.StudyPrefetcher .item {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.StudyPrefetcher .item.cached {
|
||||
background-color: black;
|
||||
}
|
||||
@@ -1,29 +1,93 @@
|
||||
import { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import OHIF from "@ohif/core";
|
||||
import React, { useEffect } from 'react';
|
||||
import { classes, utils } from '@ohif/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import cs from 'cornerstone-core';
|
||||
|
||||
const { StudyPrefetcher } = OHIF.classes;
|
||||
import './StudyPrefetcher.css';
|
||||
|
||||
class StudyPrefetcherComponent extends Component {
|
||||
static propTypes = {
|
||||
studies: PropTypes.array
|
||||
};
|
||||
const StudyPrefetcher = ({ studies, options }) => {
|
||||
useEffect(() => {
|
||||
const studyPrefetcher = classes.StudyPrefetcher.getInstance(
|
||||
studies,
|
||||
options
|
||||
);
|
||||
const studiesMetadata = studies.map(s =>
|
||||
utils.studyMetadataManager.get(s.StudyInstanceUID)
|
||||
);
|
||||
studyPrefetcher.setStudies(studiesMetadata);
|
||||
|
||||
componentDidMount() {
|
||||
const { studies } = this.props;
|
||||
const onNewImage = ({ detail }) => {
|
||||
/**
|
||||
* When images are cached the viewport will load instantly and
|
||||
* the display sets will not be available at this point in time.
|
||||
*
|
||||
* This code add display sets and updates the study prefetcher metadata.
|
||||
*/
|
||||
const studiesMetadata = studies.map(s => {
|
||||
const studyMetadata = utils.studyMetadataManager.get(
|
||||
s.StudyInstanceUID
|
||||
);
|
||||
const displaySets = studyMetadata.getDisplaySets();
|
||||
if (!displaySets || displaySets.length < 1) {
|
||||
s.displaySets.forEach(ds => studyMetadata.addDisplaySet(ds));
|
||||
}
|
||||
return studyMetadata;
|
||||
});
|
||||
studyPrefetcher.setStudies(studiesMetadata);
|
||||
|
||||
this.studyPrefetcher = StudyPrefetcher.getInstance();
|
||||
this.studyPrefetcher.setStudies(studies);
|
||||
}
|
||||
const study = studyPrefetcher.getStudy(detail.image);
|
||||
const series = studyPrefetcher.getSeries(study, detail.image);
|
||||
const instance = studyPrefetcher.getInstance(series, detail.image);
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
if (study.displaySets && study.displaySets.length > 0) {
|
||||
const {
|
||||
displaySetInstanceUID,
|
||||
} = studyPrefetcher.getDisplaySetBySOPInstanceUID(
|
||||
study.displaySets,
|
||||
instance
|
||||
);
|
||||
studyPrefetcher.prefetch(detail.element, displaySetInstanceUID);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
// Stop prefetching when we close the viewer
|
||||
this.studyPrefetcher.destroy();
|
||||
}
|
||||
}
|
||||
const onElementEnabled = ({ detail }) => {
|
||||
detail.element.addEventListener(cs.EVENTS.NEW_IMAGE, onNewImage);
|
||||
};
|
||||
|
||||
export default StudyPrefetcherComponent;
|
||||
cs.events.addEventListener(cs.EVENTS.ELEMENT_ENABLED, onElementEnabled);
|
||||
|
||||
return () => {
|
||||
cs.events.removeEventListener(
|
||||
cs.EVENTS.ELEMENT_ENABLED,
|
||||
onElementEnabled
|
||||
);
|
||||
studyPrefetcher.destroy();
|
||||
};
|
||||
}, [options, studies]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
StudyPrefetcher.propTypes = {
|
||||
studies: PropTypes.array.isRequired,
|
||||
options: PropTypes.shape({
|
||||
enabled: PropTypes.bool,
|
||||
order: PropTypes.string,
|
||||
displaySetCount: PropTypes.number,
|
||||
preventCache: PropTypes.bool,
|
||||
prefetchDisplaySetsTimeout: PropTypes.number,
|
||||
includeActiveDisplaySet: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
StudyPrefetcher.defaultProps = {
|
||||
options: {
|
||||
order: 'closest',
|
||||
displaySetCount: 1,
|
||||
preventCache: false,
|
||||
prefetchDisplaySetsTimeout: 300,
|
||||
includeActiveDisplaySet: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default StudyPrefetcher;
|
||||
@@ -1,44 +1,11 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import { connect } from 'react-redux';
|
||||
import { StudyBrowser } from '@ohif/ui';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import findDisplaySetByUID from './findDisplaySetByUID';
|
||||
import { servicesManager } from './../App.js';
|
||||
|
||||
const { studyMetadataManager } = OHIF.utils;
|
||||
import { StudyBrowser } from '../../../ui/src/components/studyBrowser/StudyBrowser';
|
||||
|
||||
const { setActiveViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
// TODO
|
||||
// - Determine in which display set is active from Redux (activeViewportIndex and layout viewportData)
|
||||
// - Pass in errors and stack loading progress from Redux
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
// If we know that the stack loading progress details have changed,
|
||||
// we can try to update the component state so that the thumbnail
|
||||
// progress bar is updated
|
||||
const stackLoadingProgressMap = state.loading.progress;
|
||||
const studiesWithLoadingData = cloneDeep(ownProps.studies);
|
||||
|
||||
studiesWithLoadingData.forEach(study => {
|
||||
study.thumbnails.forEach(data => {
|
||||
const { displaySetInstanceUID } = data;
|
||||
const stackId = `StackProgress:${displaySetInstanceUID}`;
|
||||
const stackProgressData = stackLoadingProgressMap[stackId];
|
||||
|
||||
let stackPercentComplete = 0;
|
||||
if (stackProgressData) {
|
||||
stackPercentComplete = stackProgressData.percentComplete;
|
||||
}
|
||||
|
||||
data.stackPercentComplete = stackPercentComplete;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
studies: studiesWithLoadingData,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
onThumbnailClick: displaySetInstanceUID => {
|
||||
@@ -47,10 +14,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const { LoggerService, UINotificationService } = servicesManager.services;
|
||||
|
||||
if (displaySet.isDerived) {
|
||||
const { Modality } = displaySet;
|
||||
if (Modality === 'SEG' && servicesManager) {
|
||||
const {LoggerService, UINotificationService} = servicesManager.services;
|
||||
const onDisplaySetLoadFailureHandler = error => {
|
||||
LoggerService.error({ error, message: error.message });
|
||||
UINotificationService.show({
|
||||
@@ -61,33 +29,66 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const {referencedDisplaySet, activatedLabelmapPromise} = displaySet.getSourceDisplaySet(
|
||||
const {
|
||||
referencedDisplaySet,
|
||||
activatedLabelmapPromise,
|
||||
} = displaySet.getSourceDisplaySet(
|
||||
ownProps.studyMetadata,
|
||||
true,
|
||||
onDisplaySetLoadFailureHandler
|
||||
);
|
||||
displaySet = referencedDisplaySet;
|
||||
|
||||
activatedLabelmapPromise.then((activatedLabelmapIndex) => {
|
||||
const selectionFired = new CustomEvent("extensiondicomsegmentationsegselected", {
|
||||
"detail": {"activatedLabelmapIndex":activatedLabelmapIndex}
|
||||
});
|
||||
activatedLabelmapPromise.then(activatedLabelmapIndex => {
|
||||
const selectionFired = new CustomEvent(
|
||||
'extensiondicomsegmentationsegselected',
|
||||
{
|
||||
detail: { activatedLabelmapIndex: activatedLabelmapIndex },
|
||||
}
|
||||
);
|
||||
document.dispatchEvent(selectionFired);
|
||||
});
|
||||
|
||||
} else {
|
||||
displaySet = displaySet.getSourceDisplaySet(ownProps.studyMetadata);
|
||||
}
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error(
|
||||
const error = new Error(
|
||||
`Referenced series for ${Modality} dataset not present.`
|
||||
);
|
||||
const message = `Referenced series for ${Modality} dataset not present.`;
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Fail to load series',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error('Source data not present');
|
||||
}
|
||||
if (!displaySet) {
|
||||
const error = new Error('Source data not present');
|
||||
const message = 'Source data not present';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Fail to load series',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
if (displaySet.isModalitySupported === false) {
|
||||
const error = new Error('Modality not supported');
|
||||
const message = 'Modality not supported';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Fail to load series',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(setActiveViewportSpecificData(displaySet));
|
||||
@@ -96,7 +97,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
};
|
||||
|
||||
const ConnectedStudyBrowser = connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(StudyBrowser);
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { connect } from "react-redux";
|
||||
import StudyLoadingMonitor from "../components/StudyLoadingMonitor.js";
|
||||
import OHIF from "@ohif/core";
|
||||
|
||||
const {
|
||||
setStudyLoadingProgress,
|
||||
clearStudyLoadingProgress
|
||||
} = OHIF.redux.actions;
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setStudyLoadingProgress: (progressId, progressData) => {
|
||||
dispatch(setStudyLoadingProgress(progressId, progressData));
|
||||
},
|
||||
clearStudyLoadingProgress: progressId => {
|
||||
dispatch(clearStudyLoadingProgress(progressId));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedStudyLoadingMonitor = connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(StudyLoadingMonitor);
|
||||
|
||||
export default ConnectedStudyLoadingMonitor;
|
||||
@@ -96,6 +96,8 @@ class ToolbarRow extends Component {
|
||||
value: menuOption.target,
|
||||
icon: menuOption.icon,
|
||||
bottomLabel: menuOption.label,
|
||||
badgeNumber: menuOption.badgeNumber,
|
||||
stateEvent: menuOption.stateEvent,
|
||||
};
|
||||
const from = menuOption.from || 'right';
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useLogger } from '@ohif/ui';
|
||||
|
||||
import OHIF, { MODULE_TYPES, DICOMSR } from '@ohif/core';
|
||||
import { withDialog } from '@ohif/ui';
|
||||
import moment from 'moment';
|
||||
|
||||
import ConnectedHeader from './ConnectedHeader.js';
|
||||
import ToolbarRow from './ToolbarRow.js';
|
||||
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
|
||||
@@ -14,7 +13,6 @@ import SidePanel from './../components/SidePanel.js';
|
||||
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
|
||||
import { extensionManager } from './../App.js';
|
||||
import { ReconstructionIssues } from './../../../core/src/enums.js';
|
||||
import dcmjs from 'dcmjs';
|
||||
|
||||
// Contexts
|
||||
import WhiteLabelingContext from '../context/WhiteLabelingContext.js';
|
||||
@@ -22,8 +20,10 @@ import UserManagerContext from '../context/UserManagerContext';
|
||||
import AppContext from '../context/AppContext';
|
||||
|
||||
import './Viewer.css';
|
||||
import { finished } from 'stream';
|
||||
import { cornerstoneWADOImageLoader } from 'cornerstone-wado-image-loader';
|
||||
import StudyPrefetcher from '../components/StudyPrefetcher.js';
|
||||
import StudyLoadingMonitor from '../components/StudyLoadingMonitor';
|
||||
|
||||
const { studyMetadataManager } = OHIF.utils;
|
||||
|
||||
class Viewer extends Component {
|
||||
static propTypes = {
|
||||
@@ -197,11 +197,17 @@ class Viewer extends Component {
|
||||
]);
|
||||
}
|
||||
|
||||
const activeViewport = this.props.viewports[this.props.activeViewportIndex];
|
||||
const activeDisplaySetInstanceUID =
|
||||
activeViewport ? activeViewport.displaySetInstanceUID : undefined;
|
||||
const activeViewport = this.props.viewports[
|
||||
this.props.activeViewportIndex
|
||||
];
|
||||
const activeDisplaySetInstanceUID = activeViewport
|
||||
? activeViewport.displaySetInstanceUID
|
||||
: undefined;
|
||||
this.setState({
|
||||
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
|
||||
thumbnails: _mapStudiesToThumbnails(
|
||||
studies,
|
||||
activeDisplaySetInstanceUID
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -211,24 +217,31 @@ class Viewer extends Component {
|
||||
studies,
|
||||
isStudyLoaded,
|
||||
activeViewportIndex,
|
||||
viewports
|
||||
viewports,
|
||||
} = this.props;
|
||||
|
||||
const activeViewport = viewports[activeViewportIndex];
|
||||
const activeDisplaySetInstanceUID =
|
||||
activeViewport ? activeViewport.displaySetInstanceUID : undefined;
|
||||
const activeDisplaySetInstanceUID = activeViewport
|
||||
? activeViewport.displaySetInstanceUID
|
||||
: undefined;
|
||||
|
||||
const prevActiveViewport = prevProps.viewports[prevProps.activeViewportIndex];
|
||||
const prevActiveDisplaySetInstanceUID =
|
||||
prevActiveViewport ? prevActiveViewport.displaySetInstanceUID : undefined;
|
||||
const prevActiveViewport =
|
||||
prevProps.viewports[prevProps.activeViewportIndex];
|
||||
const prevActiveDisplaySetInstanceUID = prevActiveViewport
|
||||
? prevActiveViewport.displaySetInstanceUID
|
||||
: undefined;
|
||||
|
||||
if (studies !== prevProps.studies ||
|
||||
if (
|
||||
studies !== prevProps.studies ||
|
||||
activeViewportIndex !== prevProps.activeViewportIndex ||
|
||||
activeDisplaySetInstanceUID !== prevActiveDisplaySetInstanceUID
|
||||
) {
|
||||
|
||||
) {
|
||||
this.setState({
|
||||
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
|
||||
thumbnails: _mapStudiesToThumbnails(
|
||||
studies,
|
||||
activeDisplaySetInstanceUID
|
||||
),
|
||||
activeDisplaySetInstanceUID,
|
||||
});
|
||||
}
|
||||
if (isStudyLoaded && isStudyLoaded !== prevProps.isStudyLoaded) {
|
||||
@@ -288,14 +301,12 @@ class Viewer extends Component {
|
||||
</UserManagerContext.Consumer>
|
||||
)}
|
||||
</WhiteLabelingContext.Consumer>
|
||||
|
||||
{/* TOOLBAR */}
|
||||
<ErrorBoundaryDialog context="ToolbarRow">
|
||||
<ToolbarRow
|
||||
activeViewport={
|
||||
this.props.viewports[this.props.activeViewportIndex]
|
||||
}
|
||||
isDerivedDisplaySetsLoaded={this.props.isDerivedDisplaySetsLoaded}
|
||||
isLeftSidePanelOpen={this.state.isLeftSidePanelOpen}
|
||||
isRightSidePanelOpen={this.state.isRightSidePanelOpen}
|
||||
selectedLeftSidePanel={
|
||||
@@ -332,10 +343,9 @@ class Viewer extends Component {
|
||||
studies={this.props.studies}
|
||||
/>
|
||||
</ErrorBoundaryDialog>
|
||||
|
||||
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
|
||||
{/*<StudyPrefetcher studies={this.props.studies} />*/}
|
||||
|
||||
<AppContext.Consumer>
|
||||
{appContext => <StudyLoadingMonitor studies={this.props.studies} />}
|
||||
</AppContext.Consumer>
|
||||
{/* VIEWPORTS + SIDEPANELS */}
|
||||
<div className="FlexboxLayout">
|
||||
{/* LEFT */}
|
||||
@@ -348,10 +358,24 @@ class Viewer extends Component {
|
||||
activeIndex={this.props.activeViewportIndex}
|
||||
/>
|
||||
) : (
|
||||
<ConnectedStudyBrowser
|
||||
studies={this.state.thumbnails}
|
||||
studyMetadata={this.props.studies}
|
||||
/>
|
||||
<AppContext.Consumer>
|
||||
{appContext => {
|
||||
const { appConfig } = appContext;
|
||||
const { studyPrefetcher } = appConfig;
|
||||
const { thumbnails } = this.state;
|
||||
return (
|
||||
<ConnectedStudyBrowser
|
||||
studies={thumbnails}
|
||||
studyMetadata={this.props.studies}
|
||||
showThumbnailProgressBar={
|
||||
studyPrefetcher &&
|
||||
studyPrefetcher.enabled &&
|
||||
studyPrefetcher.displayProgress
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
)}
|
||||
</SidePanel>
|
||||
</ErrorBoundaryDialog>
|
||||
@@ -359,6 +383,22 @@ class Viewer extends Component {
|
||||
{/* MAIN */}
|
||||
<div className={classNames('main-content')}>
|
||||
<ErrorBoundaryDialog context="ViewerMain">
|
||||
<AppContext.Consumer>
|
||||
{appContext => {
|
||||
const { appConfig } = appContext;
|
||||
const { studyPrefetcher } = appConfig;
|
||||
const { studies } = this.props;
|
||||
return (
|
||||
studyPrefetcher &&
|
||||
studyPrefetcher.enabled && (
|
||||
<StudyPrefetcher
|
||||
studies={studies}
|
||||
options={studyPrefetcher}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
<ConnectedViewerMain
|
||||
studies={this.props.studies}
|
||||
isStudyLoaded={this.props.isStudyLoaded}
|
||||
@@ -391,6 +431,31 @@ class Viewer extends Component {
|
||||
|
||||
export default withDialog(Viewer);
|
||||
|
||||
/**
|
||||
* Async function to check if the displaySet has any derived one
|
||||
*
|
||||
* @param {*object} displaySet
|
||||
* @param {*object} study
|
||||
* @returns {bool}
|
||||
*/
|
||||
const _checkForDerivedDisplaySets = async function(displaySet, study) {
|
||||
let derivedDisplaySetsNumber = 0;
|
||||
if (
|
||||
displaySet.Modality &&
|
||||
!['SEG', 'SR', 'RTSTRUCT', 'RTDOSE'].includes(displaySet.Modality)
|
||||
) {
|
||||
const studyMetadata = studyMetadataManager.get(study.StudyInstanceUID);
|
||||
|
||||
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||
});
|
||||
|
||||
derivedDisplaySetsNumber = derivedDisplaySets.length;
|
||||
}
|
||||
|
||||
return derivedDisplaySetsNumber > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Async function to check if there are any inconsistences in the series.
|
||||
*
|
||||
@@ -410,31 +475,46 @@ export default withDialog(Viewer);
|
||||
* @param {*object} displaySet
|
||||
* @returns {[string]} an array of strings containing the warnings
|
||||
*/
|
||||
const _checkForSeriesInconsistencesWarnings = async function (displaySet, studies) {
|
||||
const _checkForSeriesInconsistencesWarnings = async function(
|
||||
displaySet,
|
||||
studies
|
||||
) {
|
||||
if (displaySet.inconsistencyWarnings) {
|
||||
// warnings already checked and cached in displaySet
|
||||
return displaySet.inconsistencyWarnings;
|
||||
}
|
||||
|
||||
const inconsistencyWarnings = [];
|
||||
|
||||
if (displaySet.Modality !== 'SEG') {
|
||||
if (displaySet.reconstructionIssues && displaySet.reconstructionIssues.length !== 0) {
|
||||
if (
|
||||
displaySet.reconstructionIssues &&
|
||||
displaySet.reconstructionIssues.length !== 0
|
||||
) {
|
||||
displaySet.reconstructionIssues.forEach(warning => {
|
||||
switch (warning) {
|
||||
case ReconstructionIssues.DATASET_4D:
|
||||
inconsistencyWarnings.push('The dataset is 4D.');
|
||||
break;
|
||||
case ReconstructionIssues.VARYING_IMAGESDIMENSIONS:
|
||||
inconsistencyWarnings.push('The dataset frames have different dimensions (rows, columns).');
|
||||
inconsistencyWarnings.push(
|
||||
'The dataset frames have different dimensions (rows, columns).'
|
||||
);
|
||||
break;
|
||||
case ReconstructionIssues.VARYING_IMAGESCOMPONENTS:
|
||||
inconsistencyWarnings.push('The dataset frames have different components (Sample per pixel).');
|
||||
inconsistencyWarnings.push(
|
||||
'The dataset frames have different components (Sample per pixel).'
|
||||
);
|
||||
break;
|
||||
case ReconstructionIssues.VARYING_IMAGESORIENTATION:
|
||||
inconsistencyWarnings.push('The dataset frames have different orientation.');
|
||||
inconsistencyWarnings.push(
|
||||
'The dataset frames have different orientation.'
|
||||
);
|
||||
break;
|
||||
case ReconstructionIssues.IRREGULAR_SPACING:
|
||||
inconsistencyWarnings.push('The dataset frames have different pixel spacing.');
|
||||
inconsistencyWarnings.push(
|
||||
'The dataset frames have different pixel spacing.'
|
||||
);
|
||||
break;
|
||||
case ReconstructionIssues.MULTIFFRAMES:
|
||||
inconsistencyWarnings.push('The dataset is a multiframes.');
|
||||
@@ -443,39 +523,56 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
|
||||
break;
|
||||
}
|
||||
});
|
||||
inconsistencyWarnings.push('The datasets is not a reconstructable 3D volume. MPR mode is not available.');
|
||||
inconsistencyWarnings.push(
|
||||
'The datasets is not a reconstructable 3D volume. MPR mode is not available.'
|
||||
);
|
||||
}
|
||||
|
||||
if (displaySet.missingFrames &&
|
||||
if (
|
||||
displaySet.missingFrames &&
|
||||
(!displaySet.reconstructionIssues ||
|
||||
(displaySet.reconstructionIssues && !displaySet.reconstructionIssues.find(warn => warn === ReconstructionIssues.DATASET_4D)))) {
|
||||
inconsistencyWarnings.push('The datasets is missing frames: ' + displaySet.missingFrames + '.');
|
||||
(displaySet.reconstructionIssues &&
|
||||
!displaySet.reconstructionIssues.find(
|
||||
warn => warn === ReconstructionIssues.DATASET_4D
|
||||
)))
|
||||
) {
|
||||
inconsistencyWarnings.push(
|
||||
'The datasets is missing frames: ' + displaySet.missingFrames + '.'
|
||||
);
|
||||
}
|
||||
|
||||
if (displaySet.isModalitySupported === false) {
|
||||
inconsistencyWarnings.push('The datasets modality is not supported.');
|
||||
}
|
||||
} else {
|
||||
const segMetadata = displaySet.metadata;
|
||||
if (!segMetadata) {
|
||||
displaySet.inconsistencyWarnings = inconsistencyWarnings;
|
||||
return inconsistencyWarnings;
|
||||
}
|
||||
|
||||
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
|
||||
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
|
||||
studies,
|
||||
false
|
||||
);
|
||||
if (!referencedDisplaySet) {
|
||||
displaySet.inconsistencyWarnings = inconsistencyWarnings;
|
||||
return inconsistencyWarnings;
|
||||
}
|
||||
|
||||
const imageIds = referencedDisplaySet.images.map(image => image.getImageId());
|
||||
const imageIds = referencedDisplaySet.images.map(image =>
|
||||
image.getImageId()
|
||||
);
|
||||
if (!imageIds || imageIds.length === 0) {
|
||||
displaySet.inconsistencyWarnings = inconsistencyWarnings;
|
||||
return inconsistencyWarnings;
|
||||
}
|
||||
|
||||
for (
|
||||
let i = 0, groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length;
|
||||
let i = 0,
|
||||
groupsLen = segMetadata.PerFrameFunctionalGroupsSequence.length;
|
||||
i < groupsLen;
|
||||
++i
|
||||
) {
|
||||
const PerFrameFunctionalGroups = segMetadata.PerFrameFunctionalGroupsSequence[i];
|
||||
const PerFrameFunctionalGroups =
|
||||
segMetadata.PerFrameFunctionalGroupsSequence[i];
|
||||
if (!PerFrameFunctionalGroups) {
|
||||
continue;
|
||||
}
|
||||
@@ -485,29 +582,27 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
|
||||
SourceImageSequence = segMetadata.SourceImageSequence[i];
|
||||
} else if (PerFrameFunctionalGroups.DerivationImageSequence) {
|
||||
SourceImageSequence =
|
||||
PerFrameFunctionalGroups.DerivationImageSequence
|
||||
.SourceImageSequence;
|
||||
PerFrameFunctionalGroups.DerivationImageSequence.SourceImageSequence;
|
||||
}
|
||||
if (!SourceImageSequence) {
|
||||
if (inconsistencyWarnings.length === 0) {
|
||||
const warningMessage = 'The segmentation ' +
|
||||
const warningMessage =
|
||||
'The segmentation ' +
|
||||
'has frames out of plane respect to the source images.';
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const {
|
||||
ReferencedSOPInstanceUID,
|
||||
} = SourceImageSequence;
|
||||
const { ReferencedSOPInstanceUID } = SourceImageSequence;
|
||||
|
||||
const imageId = imageIds.find(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
"sopCommonModule",
|
||||
imageId
|
||||
'sopCommonModule',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
return sopCommonModule.sopInstanceUID === ReferencedSOPInstanceUID;
|
||||
@@ -517,33 +612,32 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceImageMetadata = cornerstone.metaData.get(
|
||||
"instance",
|
||||
imageId
|
||||
);
|
||||
const sourceImageMetadata = cornerstone.metaData.get('instance', imageId);
|
||||
if (
|
||||
segMetadata.Rows !== sourceImageMetadata.Rows ||
|
||||
segMetadata.Columns !== sourceImageMetadata.Columns
|
||||
) {
|
||||
const warningMessage = 'The segmentation ' +
|
||||
const warningMessage =
|
||||
'The segmentation ' +
|
||||
'has frames with different geometry ' +
|
||||
'dimensions (Rows and Columns) respect to the source images.';
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (inconsistencyWarnings.length !== 0) {
|
||||
const warningMessage = 'The segmentation format is not supported yet. ' +
|
||||
const warningMessage =
|
||||
'The segmentation format is not supported yet. ' +
|
||||
'The segmentation data (segments) could not be loaded.';
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
inconsistencyWarnings.push(warningMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// cache the warnings
|
||||
displaySet.inconsistencyWarnings = inconsistencyWarnings;
|
||||
return inconsistencyWarnings;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if display set is active, i.e. if the series is currently shown
|
||||
@@ -562,30 +656,42 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
|
||||
* @param {string} activeDisplaySetInstanceUID
|
||||
* @returns {boolean} is active.
|
||||
*/
|
||||
const _isDisplaySetActive = function(displaySet, studies, activeDisplaySetInstanceUID) {
|
||||
const _isDisplaySetActive = function(
|
||||
displaySet,
|
||||
studies,
|
||||
activeDisplaySetInstanceUID
|
||||
) {
|
||||
let active = false;
|
||||
|
||||
const {
|
||||
displaySetInstanceUID,
|
||||
} = displaySet;
|
||||
const { displaySetInstanceUID } = displaySet;
|
||||
|
||||
// TO DO: in the future, we could possibly support new modalities
|
||||
// we should have a list of all modalities here, instead of having hard coded checks
|
||||
if (displaySet.Modality !== 'SEG' &&
|
||||
if (
|
||||
displaySet.Modality !== 'SEG' &&
|
||||
displaySet.Modality !== 'RTSTRUCT' &&
|
||||
displaySet.Modality !== 'RTDOSE') {
|
||||
displaySet.Modality !== 'RTDOSE'
|
||||
) {
|
||||
active = activeDisplaySetInstanceUID === displaySetInstanceUID;
|
||||
} else if (displaySet.getSourceDisplaySet){
|
||||
} else if (displaySet.getSourceDisplaySet) {
|
||||
if (displaySet.Modality === 'SEG') {
|
||||
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
|
||||
active = referencedDisplaySet ?
|
||||
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
|
||||
false;
|
||||
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
|
||||
studies,
|
||||
false
|
||||
);
|
||||
active = referencedDisplaySet
|
||||
? activeDisplaySetInstanceUID ===
|
||||
referencedDisplaySet.displaySetInstanceUID
|
||||
: false;
|
||||
} else {
|
||||
const referencedDisplaySet = displaySet.getSourceDisplaySet(studies, false);
|
||||
active = referencedDisplaySet ?
|
||||
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
|
||||
false;
|
||||
const referencedDisplaySet = displaySet.getSourceDisplaySet(
|
||||
studies,
|
||||
false
|
||||
);
|
||||
active = referencedDisplaySet
|
||||
? activeDisplaySetInstanceUID ===
|
||||
referencedDisplaySet.displaySetInstanceUID
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +715,6 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
|
||||
const {
|
||||
displaySetInstanceUID,
|
||||
SeriesDescription,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
SeriesNumber,
|
||||
} = displaySet;
|
||||
@@ -629,19 +734,30 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
|
||||
altImageText = displaySet.Modality ? displaySet.Modality : 'UN';
|
||||
}
|
||||
|
||||
const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet, studies);
|
||||
const active = _isDisplaySetActive(displaySet, studies, activeDisplaySetInstanceUID)
|
||||
const hasWarnings = _checkForSeriesInconsistencesWarnings(
|
||||
displaySet,
|
||||
studies
|
||||
);
|
||||
|
||||
const hasDerivedDisplaySets = _checkForDerivedDisplaySets(
|
||||
displaySet,
|
||||
study
|
||||
);
|
||||
|
||||
return {
|
||||
active,
|
||||
active: _isDisplaySetActive(
|
||||
displaySet,
|
||||
studies,
|
||||
activeDisplaySetInstanceUID
|
||||
),
|
||||
imageId,
|
||||
altImageText,
|
||||
displaySetInstanceUID,
|
||||
SeriesDescription,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
SeriesNumber,
|
||||
hasWarnings,
|
||||
hasDerivedDisplaySets,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -139,10 +139,11 @@ class ViewerMain extends Component {
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const { LoggerService, UINotificationService } = servicesManager.services;
|
||||
|
||||
if (displaySet.isDerived) {
|
||||
const { Modality } = displaySet;
|
||||
if (Modality === 'SEG' && servicesManager) {
|
||||
const {LoggerService, UINotificationService} = servicesManager.services;
|
||||
const onDisplaySetLoadFailureHandler = error => {
|
||||
LoggerService.error({ error, message: error.message });
|
||||
UINotificationService.show({
|
||||
@@ -153,24 +154,41 @@ class ViewerMain extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
const {referencedDisplaySet} = displaySet.getSourceDisplaySet(
|
||||
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
|
||||
this.props.studies,
|
||||
true,
|
||||
onDisplaySetLoadFailureHandler
|
||||
);
|
||||
displaySet = referencedDisplaySet;
|
||||
|
||||
} else {
|
||||
displaySet = displaySet.getSourceDisplaySet(this.props.studies);
|
||||
}
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error(
|
||||
`Referenced series for ${Modality} dataset not present.`
|
||||
);
|
||||
const error = new Error('Source data not present');
|
||||
const message = 'Source data not present';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Fail to load series',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (displaySet.isModalitySupported === false) {
|
||||
const error = new Error('Modality not supported');
|
||||
const message = 'Modality not supported';
|
||||
LoggerService.error({ error, message });
|
||||
UINotificationService.show({
|
||||
autoClose: false,
|
||||
title: 'Fail to load series',
|
||||
message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
this.props.setViewportSpecificData(viewportIndex, displaySet);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3.7
|
||||
Submodule
+1
Submodule testdata added at 47d51e15a0.
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user