Compare commits
71
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28befb1ea6 | ||
|
|
c554c5fac2 | ||
|
|
6e3a58931a | ||
|
|
5beb12a7fc | ||
|
|
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 | ||
|
|
669717d27b | ||
|
|
715dfc556a | ||
|
|
cafb7d3f7b | ||
|
|
5f92f6ec7f | ||
|
|
eea610dfe8 | ||
|
|
e8733a9bd9 | ||
|
|
d292931073 | ||
|
|
f6a0db753a | ||
|
|
8518e179ee | ||
|
|
3f40cdced7 | ||
|
|
4cf17185da | ||
|
|
b8ae92c2d4 | ||
|
|
bad67020cc |
No files matched your search
+11
-7
@@ -25,11 +25,9 @@ executors:
|
|||||||
docker:
|
docker:
|
||||||
# Primary container image where all steps run.
|
# Primary container image where all steps run.
|
||||||
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
|
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
|
||||||
- image: 'ohif/viewer-testdata:0.1-test'
|
|
||||||
|
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:14.15.0
|
- image: cimg/node:14.18
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm # Enable colors in term
|
TERM: xterm # Enable colors in term
|
||||||
QUICK_BUILD: true
|
QUICK_BUILD: true
|
||||||
@@ -119,6 +117,7 @@ jobs:
|
|||||||
command: yarn install --frozen-lockfile
|
command: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- setup_remote_docker:
|
- setup_remote_docker:
|
||||||
|
version: 19.03.13
|
||||||
docker_layer_caching: false
|
docker_layer_caching: false
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
@@ -295,6 +294,7 @@ jobs:
|
|||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: ~/repo
|
at: ~/repo
|
||||||
- setup_remote_docker:
|
- setup_remote_docker:
|
||||||
|
version: 19.03.13
|
||||||
docker_layer_caching: false
|
docker_layer_caching: false
|
||||||
- run:
|
- run:
|
||||||
name: Build and push Docker image
|
name: Build and push Docker image
|
||||||
@@ -347,7 +347,8 @@ workflows:
|
|||||||
store_artifacts: true
|
store_artifacts: true
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build:
|
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
|
yarn run build
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
||||||
@@ -375,7 +376,8 @@ workflows:
|
|||||||
store_artifacts: true
|
store_artifacts: true
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build:
|
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
|
yarn run build:package
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
||||||
@@ -408,7 +410,8 @@ workflows:
|
|||||||
store_artifacts: false
|
store_artifacts: false
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build:
|
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
|
yarn run build
|
||||||
# start server --> verify running --> percy + chrome + cypress
|
# start server --> verify running --> percy + chrome + cypress
|
||||||
command: yarn run test:e2e:dist
|
command: yarn run test:e2e:dist
|
||||||
@@ -487,7 +490,8 @@ workflows:
|
|||||||
store_artifacts: false
|
store_artifacts: false
|
||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build:
|
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
|
yarn run build
|
||||||
# start server --> verify running --> percy + chrome + cypress
|
# start server --> verify running --> percy + chrome + cypress
|
||||||
command: yarn run test:e2e:dist
|
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.
|
||||||
@@ -9,6 +9,10 @@ src/version.js
|
|||||||
junit.xml
|
junit.xml
|
||||||
coverage/
|
coverage/
|
||||||
.docz/
|
.docz/
|
||||||
|
*.js.map
|
||||||
|
.yarn
|
||||||
|
platform/docs/.docusaurus
|
||||||
|
platform/ui/storybook-static
|
||||||
|
|
||||||
# YALC (for Erik)
|
# YALC (for Erik)
|
||||||
.yalc
|
.yalc
|
||||||
@@ -34,3 +38,4 @@ screenshots/
|
|||||||
|
|
||||||
# Locize settings
|
# Locize settings
|
||||||
.locize
|
.locize
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "testdata"]
|
||||||
|
path = testdata
|
||||||
|
url = https://github.com/OHIF/viewer-testdata-dicomweb.git
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
14.18.0
|
||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
mode: isProdBuild ? 'production' : 'development',
|
mode: isProdBuild ? 'production' : 'development',
|
||||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-eval-source-map',
|
devtool: isProdBuild ? 'source-map' : 'eval-cheap-module-source-map',
|
||||||
entry: {
|
entry: {
|
||||||
app: `${SRC_DIR}/index.js`,
|
app: `${SRC_DIR}/index.js`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
cff-version: 1.2.0
|
||||||
|
message: "If you use this software, please cite it as below."
|
||||||
|
authors:
|
||||||
|
- family-names: "Ziegler"
|
||||||
|
given-names: "Erik"
|
||||||
|
orcid: "https://orcid.org/0000-0003-1857-8129"
|
||||||
|
- family-names: "Urban"
|
||||||
|
given-names: "Trinity"
|
||||||
|
- family-names: "Brown"
|
||||||
|
given-names: "Danny"
|
||||||
|
- family-names: "Petts"
|
||||||
|
given-names: "James"
|
||||||
|
- family-names: "Pieper"
|
||||||
|
given-names: "Steve D"
|
||||||
|
- family-names: "Lewis"
|
||||||
|
given-names: "Rob"
|
||||||
|
- family-names: "Hafey"
|
||||||
|
given-names: "Chris"
|
||||||
|
- family-names: "Harris"
|
||||||
|
given-names: "Gordon J"
|
||||||
|
title: "Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework for Building Web-Based Imaging Applications to Support Cancer Research"
|
||||||
|
doi: 10.1200/CCI.19.00131
|
||||||
|
version: 1.0.0
|
||||||
|
url: "https://github.com/OHIF/Viewers"
|
||||||
+1
-1
@@ -53,7 +53,7 @@ RUN yarn run build
|
|||||||
|
|
||||||
# Stage 2: Bundle the built application into a Docker container
|
# Stage 2: Bundle the built application into a Docker container
|
||||||
# which runs Nginx using Alpine Linux
|
# which runs Nginx using Alpine Linux
|
||||||
FROM nginx:1.15.5-alpine
|
FROM nginx:1.21.1-alpine
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
RUN rm -rf /etc/nginx/conf.d
|
RUN rm -rf /etc/nginx/conf.d
|
||||||
COPY .docker/Viewer-v2.x /etc/nginx/conf.d
|
COPY .docker/Viewer-v2.x /etc/nginx/conf.d
|
||||||
|
|||||||
@@ -273,6 +273,17 @@ development of OHIF Viewer:
|
|||||||
Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
|
Leidos Biomedical Research under Task Order HHSN26100071 from NCI.
|
||||||
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
|
[IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized
|
||||||
version of the OHIF Viewer.
|
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
|
## 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
|
setups that accomplish that. Please note, noticably absent is user account
|
||||||
control.
|
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
|
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
|
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)
|
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
|
### 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,
|
requests. Nginx is open source software for web serving, reverse proxying,
|
||||||
caching, and more. It's designed for maximum performance and stability --
|
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.
|
allowing us to more reliably serve content than Orthanc's built-in server can.
|
||||||
|
|
||||||
More specifically, we accomplish this by using a
|
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.
|
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
|
> 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
|
### 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
|
tell a browser which web applications have permission to access selected
|
||||||
resources from a server at a different origin (domain, protocol, port). IE. By
|
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
|
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.**
|
**This solution uses the first approach.**
|
||||||
|
|
||||||
You can read more about CORS in this Medium article: [Understanding
|
|
||||||
CORS][understanding-cors]
|
|
||||||
|
|
||||||
### Diagram
|
### Diagram
|
||||||
|
|
||||||
This setup allows us to create a setup similar to the one pictured below:
|
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
|
||||||
- [Docker for Mac](https://docs.docker.com/docker-for-mac/)
|
- [Docker for Mac](https://docs.docker.com/docker-for-mac/)
|
||||||
- [Docker for Windows](https://docs.docker.com/docker-for-windows/)
|
- [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`
|
_Not sure if you have `docker` installed already? Try running `docker --version`
|
||||||
in command prompt or terminal_
|
in command prompt or terminal_
|
||||||
@@ -81,7 +82,7 @@ in command prompt or terminal_
|
|||||||
|
|
||||||
_Spin Things Up_
|
_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`
|
- Run `docker-compose up`
|
||||||
|
|
||||||
_Upload Your First Study_
|
_Upload Your First Study_
|
||||||
@@ -94,16 +95,16 @@ _Upload Your First Study_
|
|||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
_Exit code 137_
|
#### Exit code 137
|
||||||
|
|
||||||
This means Docker ran out of memory. Open Docker Desktop, go to the `advanced`
|
This means Docker ran out of memory. Open Docker Desktop, go to the `advanced`
|
||||||
tab, and increase the amount of Memory available.
|
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`
|
Use this one with caution: `docker system prune`
|
||||||
|
|
||||||
_X is already running_
|
#### X is already running
|
||||||
|
|
||||||
Stop running all containers:
|
Stop running all containers:
|
||||||
|
|
||||||
@@ -120,15 +121,12 @@ likely want to update:
|
|||||||
#### OHIF Viewer
|
#### OHIF Viewer
|
||||||
|
|
||||||
The OHIF Viewer's configuration is imported from a static `.js` file. The
|
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
|
determined by the env variable: `APP_CONFIG`. You can see where we set its value
|
||||||
in the `dockerfile` for this solution:
|
in the `dockerfile` for this solution:
|
||||||
|
|
||||||
`ENV APP_CONFIG=config/docker_openresty-orthanc.js`
|
`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
|
To rebuild the `webapp` image created by our `dockerfile` after updating the
|
||||||
Viewer's configuration, you can run:
|
Viewer's configuration, you can run:
|
||||||
|
|
||||||
@@ -137,7 +135,7 @@ Viewer's configuration, you can run:
|
|||||||
|
|
||||||
#### Other
|
#### Other
|
||||||
|
|
||||||
All other files are found in: `/docker/OpenResty-Orthanc/`
|
All other files in this directory include
|
||||||
|
|
||||||
| Service | Configuration | Docs |
|
| Service | Configuration | Docs |
|
||||||
| ----------------- | --------------------------------- | ------------------------------------------- |
|
| ----------------- | --------------------------------- | ------------------------------------------- |
|
||||||
@@ -236,6 +234,8 @@ members put together:
|
|||||||
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
|
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
|
||||||
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
|
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
|
||||||
<!-- SRC -->
|
<!-- 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
|
[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-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
|
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json
|
||||||
|
|||||||
@@ -67,6 +67,33 @@ window.config = {
|
|||||||
extensions: [],
|
extensions: [],
|
||||||
showStudyList: true,
|
showStudyList: true,
|
||||||
filterQueryParam: false,
|
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: {
|
servers: {
|
||||||
dicomWeb: [
|
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: [
|
hotkeys: [
|
||||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||||
@@ -97,14 +131,16 @@ window.config = {
|
|||||||
keys: ['v'],
|
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.
|
* 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
|
* Until we're able to decouple it, this key will serve as a workaround to
|
||||||
* pass it configuration.
|
* pass it configuration.
|
||||||
|
*
|
||||||
|
* @param {boolean} hideHandles Whether to show/hide annotation "handles"
|
||||||
*/
|
*/
|
||||||
cornerstoneExtensionConfig: {
|
cornerstoneExtensionConfig: {
|
||||||
/* Whether to show/hide annotation "handles" */
|
|
||||||
hideHandles: true,
|
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,95 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.12.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.6...@ohif/extension-cornerstone@2.12.7) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.12.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.12.5...@ohif/extension-cornerstone@2.12.6) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* changed new image handler conditional for 0 edge case ([#2338](https://github.com/OHIF/Viewers/issues/2338)) ([b8ae92c](https://github.com/OHIF/Viewers/commit/b8ae92c2d4f733b3ca89826118029685650b396a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.10.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.8...@ohif/extension-cornerstone@2.10.9) (2021-06-02)
|
## [2.10.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.8...@ohif/extension-cornerstone@2.10.9) (2021-06-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
## Annotate Tools Configuration
|
||||||
|
|
||||||
*We currently support one property for annotation tools.*
|
_We currently support one property for annotation tools._
|
||||||
|
|
||||||
### Hide handles
|
### 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
|
```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-tools]: https://github.com/cornerstonejs/cornerstoneTools
|
||||||
[cornerstone]: https://github.com/cornerstonejs/cornerstone
|
[cornerstone]: https://github.com/cornerstonejs/cornerstone
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
```
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.10.9",
|
"version": "2.12.7",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,12 +32,12 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.6.1",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.9",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^4.1.0",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.11",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
@@ -51,6 +51,6 @@
|
|||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"react-cornerstone-viewport": "4.0.5"
|
"react-cornerstone-viewport": "4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import OHIF from '@ohif/core';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import throttle from 'lodash.throttle';
|
import throttle from 'lodash.throttle';
|
||||||
import { setEnabledElement } from './state';
|
import { setEnabledElement } from './state';
|
||||||
|
import initSRTools from './tools/initSRTools';
|
||||||
|
|
||||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||||
const {
|
const {
|
||||||
@@ -53,7 +54,11 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
// Currently justing using escape hatch + commands
|
// Currently justing using escape hatch + commands
|
||||||
// activeTool: activeButton && activeButton.command,
|
// activeTool: activeButton && activeButton.command,
|
||||||
...dataFromStore,
|
...dataFromStore,
|
||||||
isStackPrefetchEnabled: isActive,
|
isStackPrefetchEnabled: ownProps.hasOwnProperty('isStackPrefetchEnabled')
|
||||||
|
? ownProps.isStackPrefetchEnabled
|
||||||
|
: ownProps.stackPrefetch
|
||||||
|
? ownProps.stackPrefetch.enabled
|
||||||
|
: isActive,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
frameRate,
|
frameRate,
|
||||||
//stack: viewportSpecificData.stack,
|
//stack: viewportSpecificData.stack,
|
||||||
@@ -88,6 +93,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
|||||||
plugin: 'cornerstone',
|
plugin: 'cornerstone',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
initSRTools(enabledElement);
|
||||||
},
|
},
|
||||||
|
|
||||||
onMeasurementsChanged: (event, action) => {
|
onMeasurementsChanged: (event, action) => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
|
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay';
|
||||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import checkForSRAnnotations from './tools/checkForSRAnnotations';
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
customProps: {},
|
customProps: {},
|
||||||
|
isStackPrefetchEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -23,6 +25,8 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
viewportIndex: PropTypes.number,
|
viewportIndex: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
customProps: PropTypes.object,
|
customProps: PropTypes.object,
|
||||||
|
stackPrefetch: PropTypes.object,
|
||||||
|
isStackPrefetchEnabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = 'OHIFCornerstoneViewport';
|
static id = 'OHIFCornerstoneViewport';
|
||||||
@@ -184,6 +188,8 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
|
const { viewportIndex } = this.props;
|
||||||
|
checkForSRAnnotations({ displaySet, viewportIndex });
|
||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +227,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
const { displaySet } = this.props.viewportData;
|
const { displaySet } = this.props.viewportData;
|
||||||
const { StudyInstanceUID } = displaySet;
|
const { StudyInstanceUID } = displaySet;
|
||||||
|
|
||||||
if (currentImageIdIndex > 0) {
|
if (currentImageIdIndex >= 0) {
|
||||||
this.props.onNewImage({
|
this.props.onNewImage({
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SOPInstanceUID: sopInstanceUid,
|
SOPInstanceUID: sopInstanceUid,
|
||||||
@@ -232,7 +238,12 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const warningsOverlay = props => {
|
const warningsOverlay = props => {
|
||||||
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} />
|
return (
|
||||||
|
<OHIFCornerstoneViewportOverlay
|
||||||
|
{...props}
|
||||||
|
inconsistencyWarnings={inconsistencyWarnings}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -244,10 +255,11 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
onNewImageDebounced={newImageHandler}
|
onNewImageDebounced={newImageHandler}
|
||||||
onNewImageDebounceTime={300}
|
onNewImageDebounceTime={300}
|
||||||
viewportOverlayComponent={warningsOverlay}
|
viewportOverlayComponent={warningsOverlay}
|
||||||
|
stackPrefetch={this.props.stackPrefetch}
|
||||||
|
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
|
||||||
// ~~ Connected (From REDUX)
|
// ~~ Connected (From REDUX)
|
||||||
// frameRate={frameRate}
|
// frameRate={frameRate}
|
||||||
// isPlaying={false}
|
// isPlaying={false}
|
||||||
// isStackPrefetchEnabled={true}
|
|
||||||
// onElementEnabled={() => {}}
|
// onElementEnabled={() => {}}
|
||||||
// setViewportActive{() => {}}
|
// setViewportActive{() => {}}
|
||||||
{...this.props.customProps}
|
{...this.props.customProps}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default {
|
|||||||
preRegistration({ servicesManager, configuration = {} }) {
|
preRegistration({ servicesManager, configuration = {} }) {
|
||||||
init({ servicesManager, configuration });
|
init({ servicesManager, configuration });
|
||||||
},
|
},
|
||||||
getViewportModule({ commandsManager }) {
|
getViewportModule({ commandsManager, appConfig }) {
|
||||||
const ExtendedOHIFCornerstoneViewport = props => {
|
const ExtendedOHIFCornerstoneViewport = props => {
|
||||||
/**
|
/**
|
||||||
* TODO: This appears to be used to set the redux parameters for
|
* TODO: This appears to be used to set the redux parameters for
|
||||||
@@ -48,8 +48,17 @@ export default {
|
|||||||
jumpData.refreshViewports = false;
|
jumpData.refreshViewports = false;
|
||||||
commandsManager.runCommand('jumpToImage', jumpData);
|
commandsManager.runCommand('jumpToImage', jumpData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { studyPrefetcher } = appConfig;
|
||||||
|
const isStackPrefetchEnabled =
|
||||||
|
studyPrefetcher && !studyPrefetcher.enabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
|
<OHIFCornerstoneViewport
|
||||||
|
{...props}
|
||||||
|
onNewImage={onNewImageHandler}
|
||||||
|
isStackPrefetchEnabled={isStackPrefetchEnabled}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import csTools from 'cornerstone-tools';
|
|||||||
import merge from 'lodash.merge';
|
import merge from 'lodash.merge';
|
||||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
|
import dicomSRModule from './tools/modules/dicomSRModule';
|
||||||
|
import srModuleId from './tools/id';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -15,6 +17,8 @@ import measurementServiceMappingsFactory from './utils/measurementServiceMapping
|
|||||||
export default function init({ servicesManager, configuration }) {
|
export default function init({ servicesManager, configuration }) {
|
||||||
const { UIDialogService, MeasurementService } = servicesManager.services;
|
const { UIDialogService, MeasurementService } = servicesManager.services;
|
||||||
|
|
||||||
|
csTools.register('module', srModuleId, dicomSRModule);
|
||||||
|
|
||||||
const callInputDialog = (data, event, callback) => {
|
const callInputDialog = (data, event, callback) => {
|
||||||
if (UIDialogService) {
|
if (UIDialogService) {
|
||||||
let dialogId = UIDialogService.create({
|
let dialogId = UIDialogService.create({
|
||||||
@@ -37,7 +41,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;
|
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||||
|
|
||||||
cornerstone.metaData.addProvider(
|
cornerstone.metaData.addProvider(
|
||||||
@@ -52,7 +63,7 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
autoResizeViewports: false,
|
autoResizeViewports: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
initCornerstoneTools(defaultCsToolsConfig);
|
initCornerstoneTools({ ...defaultCsToolsConfig, ...stackPrefetch });
|
||||||
|
|
||||||
const toolsGroupedByType = {
|
const toolsGroupedByType = {
|
||||||
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export default function(configuration = {}) {
|
|||||||
|
|
||||||
// Configure stack prefetch
|
// Configure stack prefetch
|
||||||
cornerstoneTools.stackPrefetch.setConfiguration({
|
cornerstoneTools.stackPrefetch.setConfiguration({
|
||||||
maxImagesToPrefetch: Infinity,
|
maxImagesToPrefetch: configuration.maxImagesToPrefetch,
|
||||||
preserveExistingPool: false,
|
preserveExistingPool: configuration.preserveExistingPool,
|
||||||
maxSimultaneousRequests: 20,
|
maxSimultaneousRequests: configuration.maxSimultaneousRequests,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
import csTools, {
|
||||||
|
importInternal,
|
||||||
|
getToolState,
|
||||||
|
toolColors,
|
||||||
|
} from 'cornerstone-tools';
|
||||||
|
|
||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
|
/** Internal imports */
|
||||||
|
import TOOL_NAMES from './constants/toolNames';
|
||||||
|
import SCOORD_TYPES from './constants/scoordTypes';
|
||||||
|
import id from './id';
|
||||||
|
|
||||||
|
/** Cornerstone 3rd party dev kit imports */
|
||||||
|
const draw = importInternal('drawing/draw');
|
||||||
|
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
|
||||||
|
const drawCircle = importInternal('drawing/drawCircle');
|
||||||
|
const drawEllipse = importInternal('drawing/drawEllipse');
|
||||||
|
const drawHandles = importInternal('drawing/drawHandles');
|
||||||
|
const drawArrow = importInternal('drawing/drawArrow');
|
||||||
|
const getNewContext = importInternal('drawing/getNewContext');
|
||||||
|
const BaseTool = importInternal('base/BaseTool');
|
||||||
|
const drawLinkedTextBox = importInternal('drawing/drawLinkedTextBox');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DICOMSRDisplayTool - Renders DICOMSR data in a read only manner (i.e. as an overlay).
|
||||||
|
*
|
||||||
|
* This is a generic render tool.
|
||||||
|
*
|
||||||
|
* A single tool that, given some schema, can render
|
||||||
|
* POINT, MULTIPOINT, POLYLINE, CIRCLE, and ELLIPSE
|
||||||
|
* value types for a given imageId.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @extends cornerstoneTools.BaseTool
|
||||||
|
*/
|
||||||
|
export default class DICOMSRDisplayTool extends BaseTool {
|
||||||
|
constructor(props = {}) {
|
||||||
|
const defaultProps = {
|
||||||
|
mixins: ['enabledOrDisabledBinaryTool'],
|
||||||
|
name: TOOL_NAMES.DICOM_SR_DISPLAY_TOOL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialProps = Object.assign(defaultProps, props);
|
||||||
|
|
||||||
|
super(initialProps);
|
||||||
|
|
||||||
|
this._module = csTools.getModule(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolData(evt) {
|
||||||
|
const eventData = evt.detail;
|
||||||
|
const { element } = eventData;
|
||||||
|
const module = this._module;
|
||||||
|
|
||||||
|
const toolState = getToolState(element, this.name);
|
||||||
|
|
||||||
|
if (!toolState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackingUniqueIdentifiersForElement = module.getters.trackingUniqueIdentifiersForElement(
|
||||||
|
element
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
activeIndex,
|
||||||
|
trackingUniqueIdentifiers,
|
||||||
|
} = trackingUniqueIdentifiersForElement;
|
||||||
|
|
||||||
|
const activeTrackingUniqueIdentifier =
|
||||||
|
trackingUniqueIdentifiers[activeIndex];
|
||||||
|
|
||||||
|
// Filter toolData to only render the data for the active SR.
|
||||||
|
const filteredToolData = toolState.data.filter(td =>
|
||||||
|
trackingUniqueIdentifiers.includes(td.TrackingUniqueIdentifier)
|
||||||
|
);
|
||||||
|
|
||||||
|
let shouldRepositionTextBoxes = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < filteredToolData.length; i++) {
|
||||||
|
const data = filteredToolData[i];
|
||||||
|
const { renderableData, labels } = data;
|
||||||
|
|
||||||
|
const color =
|
||||||
|
data.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier
|
||||||
|
? toolColors.getActiveColor()
|
||||||
|
: toolColors.getToolColor();
|
||||||
|
const lineWidth = 2;
|
||||||
|
const options = {
|
||||||
|
color,
|
||||||
|
lineWidth,
|
||||||
|
handleRadius: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(renderableData).forEach(GraphicType => {
|
||||||
|
const renderableDataForGraphicType = renderableData[GraphicType];
|
||||||
|
|
||||||
|
switch (GraphicType) {
|
||||||
|
case SCOORD_TYPES.POINT:
|
||||||
|
this.renderPoint(renderableDataForGraphicType, eventData, options);
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.MULTIPOINT:
|
||||||
|
this.renderMultipoint(
|
||||||
|
renderableDataForGraphicType,
|
||||||
|
eventData,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.POLYGON:
|
||||||
|
case SCOORD_TYPES.POLYLINE:
|
||||||
|
this.renderPolyLine(
|
||||||
|
renderableDataForGraphicType,
|
||||||
|
eventData,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.CIRCLE:
|
||||||
|
this.renderCircle(renderableDataForGraphicType, eventData, options);
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.ELLIPSE:
|
||||||
|
this.renderEllipse(
|
||||||
|
renderableDataForGraphicType,
|
||||||
|
eventData,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { element } = eventData;
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
if (!data.handles || !data.handles.textBox) {
|
||||||
|
const textBox = {
|
||||||
|
active: false,
|
||||||
|
hasMoved: true,
|
||||||
|
movesIndependently: false,
|
||||||
|
drawnIndependently: true,
|
||||||
|
allowedOutsideImage: true,
|
||||||
|
hasBoundingBox: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const anchorPoints = _getTextBoxAnchorPointsForRenderableData(
|
||||||
|
renderableData,
|
||||||
|
eventData
|
||||||
|
);
|
||||||
|
textBox.anchorPoints = anchorPoints;
|
||||||
|
|
||||||
|
const bottomRight = {
|
||||||
|
x: Math.max(...anchorPoints.map(point => point.x)),
|
||||||
|
y: Math.max(...anchorPoints.map(point => point.y)),
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.x = bottomRight.x;
|
||||||
|
textBox.y = bottomRight.y;
|
||||||
|
|
||||||
|
data.handles = {};
|
||||||
|
data.handles.textBox = textBox;
|
||||||
|
|
||||||
|
shouldRepositionTextBoxes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = _getTextBoxLinesFromLabels(labels);
|
||||||
|
|
||||||
|
function textBoxAnchorPoints() {
|
||||||
|
return data.handles.textBox.anchorPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(context, context => {
|
||||||
|
drawLinkedTextBox(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
data.handles.textBox,
|
||||||
|
text,
|
||||||
|
data.handles,
|
||||||
|
textBoxAnchorPoints,
|
||||||
|
color,
|
||||||
|
lineWidth,
|
||||||
|
0,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOOD -> text boxes may overlap with other annotations at the moment.
|
||||||
|
// To be fixed after we get requirements.
|
||||||
|
// if (shouldRepositionTextBoxes) {
|
||||||
|
// this.repositionTextBox(filteredToolData, eventData);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// repositionTextBox(toolData, eventData) {
|
||||||
|
// const toolBoundingBoxes = [];
|
||||||
|
|
||||||
|
// for (let i = 0; i < toolData.length; i++) {
|
||||||
|
// const toolDataI = toolData[i];
|
||||||
|
|
||||||
|
// const { textBox } = toolDataI.handles;
|
||||||
|
// const { anchorPoints } = textBox;
|
||||||
|
|
||||||
|
// const boundingBox = _getBoundingBoxFromAnchorPoints(anchorPoints);
|
||||||
|
// // Get the textbox bounding locations.
|
||||||
|
// // Get the tool extents.
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
renderPolyLine(renderableData, eventData, options) {
|
||||||
|
const { element } = eventData;
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
renderableData.forEach(points => {
|
||||||
|
draw(context, context => {
|
||||||
|
drawJoinedLines(context, element, points[0], points, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMultipoint(renderableData, eventData, options) {
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
renderableData.forEach(points => {
|
||||||
|
draw(context, context => {
|
||||||
|
drawHandles(context, eventData, points, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPoint(renderableData, eventData, options) {
|
||||||
|
// Render single point as an arrow.
|
||||||
|
const { element, image } = eventData;
|
||||||
|
const { rows, columns } = image;
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
const { color, lineWidth } = options;
|
||||||
|
|
||||||
|
// Find a suitable length for the image size.
|
||||||
|
|
||||||
|
const xOffset = columns / 10;
|
||||||
|
const yOffset = rows / 10;
|
||||||
|
|
||||||
|
renderableData.forEach(points => {
|
||||||
|
const point = points[0]; // The SCOORD type is POINT so the array length is 1.
|
||||||
|
draw(context, context => {
|
||||||
|
// Draw the arrow
|
||||||
|
const handleStartCanvas = cornerstone.pixelToCanvas(element, point);
|
||||||
|
const handleEndCanvas = cornerstone.pixelToCanvas(element, {
|
||||||
|
x: point.x + xOffset,
|
||||||
|
y: point.y + yOffset,
|
||||||
|
});
|
||||||
|
|
||||||
|
drawArrow(
|
||||||
|
context,
|
||||||
|
handleEndCanvas,
|
||||||
|
handleStartCanvas,
|
||||||
|
color,
|
||||||
|
lineWidth,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCircle(renderableData, eventData, options) {
|
||||||
|
const { element } = eventData;
|
||||||
|
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
renderableData.forEach(circle => {
|
||||||
|
const { center, radius } = circle;
|
||||||
|
|
||||||
|
drawCircle(context, element, center, radius, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEllipse(renderableData, eventData, options) {
|
||||||
|
const { element } = eventData;
|
||||||
|
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
renderableData.forEach(ellipse => {
|
||||||
|
const { corner1, corner2 } = ellipse;
|
||||||
|
|
||||||
|
drawEllipse(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
corner1,
|
||||||
|
corner2,
|
||||||
|
options,
|
||||||
|
'pixel',
|
||||||
|
0 // TODO -> Work our the initial rotation and add it here so we render appropriately rotated ellipses.
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getTextBoxLinesFromLabels(labels) {
|
||||||
|
// TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this!
|
||||||
|
|
||||||
|
const labelLength = Math.min(labels.length, 3);
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < labelLength; i++) {
|
||||||
|
const labelEntry = labels[i];
|
||||||
|
lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHORT_HAND_MAP = {
|
||||||
|
'Short Axis': 'W ',
|
||||||
|
'Long Axis': 'L ',
|
||||||
|
AREA: 'Area ',
|
||||||
|
Length: '',
|
||||||
|
CORNERSTONEFREETEXT: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function _labelToShorthand(label) {
|
||||||
|
const shortHand = SHORT_HAND_MAP[label];
|
||||||
|
|
||||||
|
if (shortHand !== undefined) {
|
||||||
|
return shortHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getTextBoxAnchorPointsForRenderableData(renderableData, eventData) {
|
||||||
|
let anchorPoints = [];
|
||||||
|
|
||||||
|
Object.keys(renderableData).forEach(GraphicType => {
|
||||||
|
const renderableDataForGraphicType = renderableData[GraphicType];
|
||||||
|
|
||||||
|
switch (GraphicType) {
|
||||||
|
case SCOORD_TYPES.POINT:
|
||||||
|
renderableDataForGraphicType.forEach(points => {
|
||||||
|
anchorPoints = [...anchorPoints, ...points];
|
||||||
|
|
||||||
|
// Add other arrow point based on image size.
|
||||||
|
const { image } = eventData;
|
||||||
|
const { rows, columns } = image;
|
||||||
|
|
||||||
|
const xOffset = columns / 10;
|
||||||
|
const yOffset = rows / 10;
|
||||||
|
const point = points[0];
|
||||||
|
|
||||||
|
anchorPoints.push({ x: point.x + xOffset, y: point.y + yOffset });
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.MULTIPOINT:
|
||||||
|
case SCOORD_TYPES.POLYLINE:
|
||||||
|
case SCOORD_TYPES.POLYGON:
|
||||||
|
renderableDataForGraphicType.forEach(points => {
|
||||||
|
anchorPoints = [...anchorPoints, ...points];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.CIRCLE:
|
||||||
|
renderableDataForGraphicType.forEach(circle => {
|
||||||
|
const { center, radius } = circle;
|
||||||
|
|
||||||
|
anchorPoints.push({ x: center.x + radius, y: center.y });
|
||||||
|
anchorPoints.push({ x: center.x - radius, y: center.y });
|
||||||
|
anchorPoints.push({ x: center.x, y: center.y + radius });
|
||||||
|
anchorPoints.push({ x: center.x, y: center.y - radius });
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.ELLIPSE:
|
||||||
|
renderableDataForGraphicType.forEach(ellipse => {
|
||||||
|
const { corner1, corner2 } = ellipse;
|
||||||
|
|
||||||
|
const halfWidth = Math.abs(corner1.x - corner2.x) / 2;
|
||||||
|
const halfHeight = Math.abs(corner1.y - corner2.y) / 2;
|
||||||
|
|
||||||
|
const center = {
|
||||||
|
x: (corner1.x + corner2.x) / 2,
|
||||||
|
y: (corner1.y + corner2.y) / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
anchorPoints.push({ x: center.x + halfWidth, y: center.y });
|
||||||
|
anchorPoints.push({ x: center.x - halfWidth, y: center.y });
|
||||||
|
anchorPoints.push({ x: center.x, y: center.y + halfHeight });
|
||||||
|
anchorPoints.push({ x: center.x, y: center.y - halfHeight });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchorPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getBoundingBoxFromAnchorPoints(anchorPoints) {
|
||||||
|
let minX = Infinity;
|
||||||
|
let maxX = -Infinity;
|
||||||
|
let minY = Infinity;
|
||||||
|
let maxY = -Infinity;
|
||||||
|
|
||||||
|
anchorPoints.forEach(point => {
|
||||||
|
const { x, y } = point;
|
||||||
|
|
||||||
|
if (x > maxX) {
|
||||||
|
maxX = x;
|
||||||
|
} else if (x < minX) {
|
||||||
|
minX = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > maxX) {
|
||||||
|
maxY = y;
|
||||||
|
} else if (y < minY) {
|
||||||
|
minY = y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import csTools from 'cornerstone-tools';
|
||||||
|
import cs from 'cornerstone-core';
|
||||||
|
import OHIF from '@ohif/core';
|
||||||
|
|
||||||
|
import { getEnabledElement } from '../state';
|
||||||
|
import id from './id';
|
||||||
|
|
||||||
|
const { studyMetadataManager } = OHIF.utils;
|
||||||
|
|
||||||
|
const checkForSRAnnotations = ({ viewportIndex, displaySet }) => {
|
||||||
|
const srModule = csTools.getModule(id);
|
||||||
|
|
||||||
|
const element = getEnabledElement(viewportIndex);
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { StudyInstanceUID } = displaySet;
|
||||||
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||||
|
if (!studyMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const srDisplaySets = studyMetadata
|
||||||
|
.getDisplaySets()
|
||||||
|
.filter(ds => ds.Modality === 'SR');
|
||||||
|
if (srDisplaySets.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { measurements: _measurements } = srDisplaySets[0];
|
||||||
|
if (!_measurements || _measurements.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const measurements = _measurements.filter(m => m.loaded === true);
|
||||||
|
const measurement = measurements[0];
|
||||||
|
if (!measurement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
srModule.setters.trackingUniqueIdentifiersForElement(
|
||||||
|
element,
|
||||||
|
measurements.map(measurement => measurement.TrackingUniqueIdentifier),
|
||||||
|
measurement
|
||||||
|
);
|
||||||
|
|
||||||
|
const { TrackingUniqueIdentifier } = measurement;
|
||||||
|
srModule.setters.activeTrackingUniqueIdentifierForElement(
|
||||||
|
element,
|
||||||
|
TrackingUniqueIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
cs.updateImage(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default checkForSRAnnotations;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
POINT: 'POINT',
|
||||||
|
MULTIPOINT: 'MULTIPOINT',
|
||||||
|
POLYLINE: 'POLYLINE',
|
||||||
|
CIRCLE: 'CIRCLE',
|
||||||
|
ELLIPSE: 'ELLIPSE',
|
||||||
|
POLYGON: 'POLYGON',
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const TOOL_NAMES = {
|
||||||
|
DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TOOL_NAMES;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const id = 'org.ohif.dicom-sr';
|
||||||
|
|
||||||
|
export default id;
|
||||||
|
|
||||||
|
const SOPClassHandlerName = 'dicom-sr';
|
||||||
|
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||||
|
export { SOPClassHandlerName, SOPClassHandlerId };
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
import DICOMSRDisplayTool from './DICOMSRDisplayTool';
|
||||||
|
import TOOL_NAMES from './constants/toolNames';
|
||||||
|
import getToolAlias from './utils/getToolAlias';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize SR cornerstone tools.
|
||||||
|
*
|
||||||
|
* @param {*} targetElement
|
||||||
|
*/
|
||||||
|
const initSRTools = targetElement => {
|
||||||
|
const primaryToolId = 'Wwwc';
|
||||||
|
const toolAlias = getToolAlias(primaryToolId); // These are 1:1 for built-in only
|
||||||
|
|
||||||
|
// ~~ MAGIC
|
||||||
|
cornerstoneTools.addToolForElement(targetElement, DICOMSRDisplayTool);
|
||||||
|
cornerstoneTools.setToolEnabledForElement(
|
||||||
|
targetElement,
|
||||||
|
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL
|
||||||
|
);
|
||||||
|
|
||||||
|
// ~~ Variants
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.LengthTool,
|
||||||
|
{
|
||||||
|
name: 'SRLength',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.ArrowAnnotateTool,
|
||||||
|
{
|
||||||
|
name: 'SRArrowAnnotate',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.BidirectionalTool,
|
||||||
|
{
|
||||||
|
name: 'SRBidirectional',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.EllipticalRoiTool,
|
||||||
|
{
|
||||||
|
name: 'SREllipticalRoi',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.RectangleRoiTool,
|
||||||
|
{
|
||||||
|
name: 'SRRectangleRoi',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
cornerstoneTools.addToolForElement(
|
||||||
|
targetElement,
|
||||||
|
cornerstoneTools.FreehandRoiTool,
|
||||||
|
{
|
||||||
|
name: 'SRFreehandRoi',
|
||||||
|
configuration: {
|
||||||
|
renderDashed: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ~~ Business as usual
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, 'PanMultiTouch', {
|
||||||
|
pointers: 2,
|
||||||
|
});
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, 'ZoomTouchPinch', {});
|
||||||
|
|
||||||
|
// TODO: Add always dashed tool alternative aliases
|
||||||
|
// TODO: or same name... alternative config?
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, {
|
||||||
|
mouseButtonMask: 1,
|
||||||
|
});
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, 'Pan', {
|
||||||
|
mouseButtonMask: 4,
|
||||||
|
});
|
||||||
|
cornerstoneTools.setToolActiveForElement(targetElement, 'Zoom', {
|
||||||
|
mouseButtonMask: 2,
|
||||||
|
});
|
||||||
|
cornerstoneTools.setToolActiveForElement(
|
||||||
|
targetElement,
|
||||||
|
'StackScrollMouseWheel',
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initSRTools;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
TrackingUniqueIdentifier: null,
|
||||||
|
trackingIdentifiersByEnabledElementUUID: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function setTrackingUniqueIdentifiersForElement(
|
||||||
|
element,
|
||||||
|
trackingUniqueIdentifiers,
|
||||||
|
activeIndex = 0
|
||||||
|
) {
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
const { uuid } = enabledElement;
|
||||||
|
|
||||||
|
state.trackingIdentifiersByEnabledElementUUID[uuid] = {
|
||||||
|
trackingUniqueIdentifiers,
|
||||||
|
activeIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveTrackingUniqueIdentifierForElement(
|
||||||
|
element,
|
||||||
|
TrackingUniqueIdentifier
|
||||||
|
) {
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
const { uuid } = enabledElement;
|
||||||
|
|
||||||
|
const trackingIdentifiersForElement =
|
||||||
|
state.trackingIdentifiersByEnabledElementUUID[uuid];
|
||||||
|
|
||||||
|
if (trackingIdentifiersForElement) {
|
||||||
|
const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex(
|
||||||
|
tuid => tuid === TrackingUniqueIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
trackingIdentifiersForElement.activeIndex = activeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrackingUniqueIdentifiersForElement(element) {
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
const { uuid } = enabledElement;
|
||||||
|
|
||||||
|
if (state.trackingIdentifiersByEnabledElementUUID[uuid]) {
|
||||||
|
return state.trackingIdentifiersByEnabledElementUUID[uuid];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { trackingUniqueIdentifiers: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
state,
|
||||||
|
getters: {
|
||||||
|
trackingUniqueIdentifiersForElement: getTrackingUniqueIdentifiersForElement,
|
||||||
|
},
|
||||||
|
setters: {
|
||||||
|
trackingUniqueIdentifiersForElement: setTrackingUniqueIdentifiersForElement,
|
||||||
|
activeTrackingUniqueIdentifierForElement: setActiveTrackingUniqueIdentifierForElement,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Get cornerstone tool alias.
|
||||||
|
*
|
||||||
|
* @param {string} toolName
|
||||||
|
* @returns tool alias
|
||||||
|
*/
|
||||||
|
export default function getToolAlias(toolName) {
|
||||||
|
let toolAlias = toolName;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'Length':
|
||||||
|
toolAlias = 'SRLength';
|
||||||
|
break;
|
||||||
|
case 'Bidirectional':
|
||||||
|
toolAlias = 'SRBidirectional';
|
||||||
|
break;
|
||||||
|
case 'ArrowAnnotate':
|
||||||
|
toolAlias = 'SRArrowAnnotate';
|
||||||
|
break;
|
||||||
|
case 'EllipticalRoi':
|
||||||
|
toolAlias = 'SREllipticalRoi';
|
||||||
|
break;
|
||||||
|
case 'FreehandRoi':
|
||||||
|
toolAlias = 'SRFreehandRoi';
|
||||||
|
break;
|
||||||
|
case 'RectangleRoi':
|
||||||
|
toolAlias = 'SRRectangleRoi';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolAlias;
|
||||||
|
}
|
||||||
@@ -3,6 +3,25 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-debugging",
|
"name": "@ohif/extension-debugging",
|
||||||
"version": "0.2.2",
|
"version": "0.3.1",
|
||||||
"description": "OHIF extension for debugging.",
|
"description": "OHIF extension for debugging.",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^2.6.0",
|
"@ohif/core": "^2.6.0",
|
||||||
"dicom-parser": "^1.8.3"
|
"dicom-parser": "^1.8.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"detect-browser": "5.1.1",
|
"detect-browser": "5.1.1",
|
||||||
"dicomweb-client": "^0.8.1",
|
"dicomweb-client": "^0.8.3",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"jszip": "^3.2.2"
|
"jszip": "^3.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,70 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.3.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.17...@ohif/extension-dicom-html@1.3.18) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.16...@ohif/extension-dicom-html@1.3.17) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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.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",
|
"name": "@ohif/extension-dicom-html",
|
||||||
"version": "1.3.10",
|
"version": "1.3.18",
|
||||||
"description": "OHIF extension for rendering structured reports to HTML",
|
"description": "OHIF extension for rendering structured reports to HTML",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ const getContentSequence = (data, level = 1) => {
|
|||||||
|
|
||||||
const root = [];
|
const root = [];
|
||||||
if (header) {
|
if (header) {
|
||||||
const HeaderDynamicLevel = `h${level}`;
|
const HeaderDynamicLevel = `h${Math.min(level, 6)}`;
|
||||||
|
|
||||||
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
|
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ const getContentSequence = (data, level = 1) => {
|
|||||||
|
|
||||||
if (key === '_meta') {
|
if (key === '_meta') {
|
||||||
const HeaderDynamicLevel = `h3`;
|
const HeaderDynamicLevel = `h3`;
|
||||||
root.push(<hr />);
|
root.push(<hr key={root.length} />);
|
||||||
root.push(
|
root.push(
|
||||||
<HeaderDynamicLevel key="Metadata">
|
<HeaderDynamicLevel key="Metadata">
|
||||||
DICOM File Meta Information
|
DICOM File Meta Information
|
||||||
@@ -217,7 +217,7 @@ class DicomHtmlViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const dataSet = this.setContentFromByteArray(this.props.byteArray);
|
this.setContentFromByteArray(this.props.byteArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentFromByteArray(byteArray) {
|
setContentFromByteArray(byteArray) {
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ const SOP_CLASS_UIDS = {
|
|||||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||||
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||||
|
COMPREHENSIVE_3D_SR: '1.2.840.10008.5.1.4.1.1.88.34',
|
||||||
PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40',
|
PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40',
|
||||||
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
|
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
|
||||||
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
|
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
|
||||||
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
||||||
ACQUISITION_CONTEXT_SR_STORAGE: '1.2.840.10008.5.1.4.1.1.88.71',
|
ACQUISITION_CONTEXT_SR_STORAGE: '1.2.840.10008.5.1.4.1.1.88.71'
|
||||||
};
|
};
|
||||||
|
|
||||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||||
@@ -46,6 +47,8 @@ const OHIFDicomHtmlSopClassHandler = {
|
|||||||
SeriesTime,
|
SeriesTime,
|
||||||
SeriesNumber,
|
SeriesNumber,
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
|
sopClassUids: sopClassUIDs,
|
||||||
|
images: series._instances,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,33 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-pdf",
|
"name": "@ohif/extension-dicom-pdf",
|
||||||
"version": "1.0.6",
|
"version": "1.1.2",
|
||||||
"description": "OHIF extension for Dicom PDF",
|
"description": "OHIF extension for Dicom PDF",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.11",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class DicomPDFViewport extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
byteArray: TypedArrayProp.uint8,
|
byteArray: TypedArrayProp.uint8,
|
||||||
|
rawPdf: PropTypes.bool,
|
||||||
useNative: PropTypes.bool,
|
useNative: PropTypes.bool,
|
||||||
viewportData: PropTypes.object,
|
viewportData: PropTypes.object,
|
||||||
activeViewportIndex: PropTypes.number,
|
activeViewportIndex: PropTypes.number,
|
||||||
@@ -45,7 +46,8 @@ class DicomPDFViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async componentDidMount() {
|
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);
|
const fileURL = this.getPDFFileUrl(dataSet, this.props.byteArray);
|
||||||
|
|
||||||
this.setState(state => ({ ...state, fileURL }));
|
this.setState(state => ({ ...state, fileURL }));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
|
||||||
import DicomPDFViewport from './DicomPDFViewport';
|
import DicomPDFViewport from './DicomPDFViewport';
|
||||||
|
import { str2ab } from '@ohif/core';
|
||||||
|
|
||||||
const { DicomLoaderService } = OHIF.utils;
|
const { DicomLoaderService } = OHIF.utils;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class OHIFDicomPDFViewport extends Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
byteArray: null,
|
byteArray: null,
|
||||||
|
rawPdf: false,
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +36,15 @@ class OHIFDicomPDFViewport extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { displaySet, studies } = this.props.viewportData;
|
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(
|
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||||
data => this.setState({ byteArray: new Uint8Array(data) }),
|
data => this.setState({ byteArray: new Uint8Array(data) }),
|
||||||
error => {
|
error => {
|
||||||
@@ -49,15 +60,16 @@ class OHIFDicomPDFViewport extends Component {
|
|||||||
viewportIndex,
|
viewportIndex,
|
||||||
activeViewportIndex,
|
activeViewportIndex,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { byteArray, error } = this.state;
|
const { byteArray, error, rawPdf } = this.state;
|
||||||
const { id, init, destroy } = OHIFDicomPDFViewport;
|
const { id, init, destroy } = OHIFDicomPDFViewport;
|
||||||
const pluginProps = { id, init, destroy };
|
const pluginProps = { id, init, destroy };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OHIFComponentPlugin {...pluginProps}>
|
<OHIFComponentPlugin {...pluginProps}>
|
||||||
{byteArray && (
|
{(byteArray) && (
|
||||||
<DicomPDFViewport
|
<DicomPDFViewport
|
||||||
byteArray={byteArray}
|
byteArray={byteArray}
|
||||||
|
rawPdf={rawPdf}
|
||||||
setViewportActive={setViewportActive}
|
setViewportActive={setViewportActive}
|
||||||
viewportIndex={viewportIndex}
|
viewportIndex={viewportIndex}
|
||||||
activeViewportIndex={activeViewportIndex}
|
activeViewportIndex={activeViewportIndex}
|
||||||
|
|||||||
@@ -3,6 +3,89 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.7.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.7.7...@ohif/extension-dicom-rt@0.7.8) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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",
|
"name": "@ohif/extension-dicom-rt",
|
||||||
"version": "0.6.13",
|
"version": "0.7.8",
|
||||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.6.1",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"gl-matrix": "^3.3.0",
|
"gl-matrix": "^3.3.0",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
|
|||||||
@@ -81,13 +81,10 @@ const RTPanel = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateStructureSets();
|
updateStructureSets();
|
||||||
|
|
||||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
document.addEventListener('rt-panel-tab-updated', updateStructureSets);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(
|
document.removeEventListener('rt-panel-tab-updated', updateStructureSets);
|
||||||
'extensiondicomrtrtloaded',
|
|
||||||
updateStructureSets
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
||||||
import id from './id.js';
|
import id from './id.js';
|
||||||
import RTPanel from './components/RTPanel/RTPanel';
|
import RTPanel from './components/RTPanel/RTPanel';
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
|
||||||
import { utils } from '@ohif/core';
|
|
||||||
const { studyMetadataManager } = utils;
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
export default {
|
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 {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
label: 'RTSTRUCT',
|
label: 'RTSTRUCT',
|
||||||
target: 'rt-panel',
|
target: 'rt-panel',
|
||||||
|
stateEvent: RTPanelTabChangedEvent,
|
||||||
isDisabled: (studies, activeViewport) => {
|
isDisabled: (studies, activeViewport) => {
|
||||||
if (!studies) {
|
if (!studies) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeViewport) {
|
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(
|
const studyMetadata = studyMetadataManager.get(
|
||||||
activeViewport.StudyInstanceUID
|
activeViewport.StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
if (!studyMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const referencedDisplaySets = studyMetadata.getDerivedDatasets({
|
const referencedDisplaySets = studyMetadata.getDerivedDatasets({
|
||||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||||
Modality: 'RTSTRUCT',
|
Modality: 'RTSTRUCT',
|
||||||
@@ -75,6 +107,10 @@ export default {
|
|||||||
['RTSTRUCT'].includes(ds.Modality)
|
['RTSTRUCT'].includes(ds.Modality)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
triggerRTPanelUpdatedEvent({
|
||||||
|
badgeNumber: referencedDisplaySets.length,
|
||||||
|
target: 'rt-panel',
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,11 @@ export default async function loadRTStruct(
|
|||||||
const sopInstanceUID = ContourImageSequence
|
const sopInstanceUID = ContourImageSequence
|
||||||
? ContourImageSequence.ReferencedSOPInstanceUID
|
? ContourImageSequence.ReferencedSOPInstanceUID
|
||||||
: _getClosestSOPInstanceUID(
|
: _getClosestSOPInstanceUID(
|
||||||
ContourData,
|
ContourData,
|
||||||
ContourGeometricType,
|
ContourGeometricType,
|
||||||
NumberOfContourPoints,
|
NumberOfContourPoints,
|
||||||
imageIdSopInstanceUidPairs
|
imageIdSopInstanceUidPairs
|
||||||
);
|
);
|
||||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||||
|
|
||||||
if (!imageId) {
|
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
|
* allows us to easily watch the module or the rtstruct loading process in any other component
|
||||||
* without subscribing to external events.
|
* without subscribing to external events.
|
||||||
*/
|
*/
|
||||||
const event = new CustomEvent('extensiondicomrtrtloaded');
|
const event = new CustomEvent('extensiondicomrtrtloaded', {
|
||||||
|
detail: {
|
||||||
|
rtStructDisplaySet,
|
||||||
|
referencedDisplaySet,
|
||||||
|
studies,
|
||||||
|
},
|
||||||
|
});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +305,9 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
|||||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
return imageIdSopInstanceUidPairsEntry
|
||||||
|
? imageIdSopInstanceUidPairsEntry.imageId
|
||||||
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||||
|
|||||||
@@ -3,6 +3,89 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.7.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.7.7...@ohif/extension-dicom-segmentation@0.7.8) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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",
|
"name": "@ohif/extension-dicom-segmentation",
|
||||||
"version": "0.6.10",
|
"version": "0.7.8",
|
||||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.6.1",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const SegmentItem = ({
|
|||||||
onClick,
|
onClick,
|
||||||
itemClass,
|
itemClass,
|
||||||
color,
|
color,
|
||||||
|
labelmap3D,
|
||||||
visible,
|
visible,
|
||||||
onVisibilityChange,
|
onVisibilityChange,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -39,7 +40,7 @@ const SegmentItem = ({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const newVisibility = !isVisible;
|
const newVisibility = !isVisible;
|
||||||
setIsVisible(newVisibility);
|
setIsVisible(newVisibility);
|
||||||
onVisibilityChange(newVisibility, index);
|
onVisibilityChange(newVisibility, index, labelmap3D);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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;
|
const index = e.detail.activatedLabelmapIndex;
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
setState(state => ({ ...state, selectedSegmentation: index }));
|
setState(state => ({ ...state, selectedSegmentation: index }));
|
||||||
} else {
|
} else {
|
||||||
cleanSegmentationComboBox();
|
cleanSegmentationComboBox();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanSegmentationComboBox = () => {
|
const cleanSegmentationComboBox = () => {
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
@@ -245,7 +249,7 @@ const SegmentationPanel = ({
|
|||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
selectedSegmentation: -1,
|
selectedSegmentation: -1,
|
||||||
}));
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
const refreshSegmentations = () => {
|
const refreshSegmentations = () => {
|
||||||
const activeViewport = getActiveViewport();
|
const activeViewport = getActiveViewport();
|
||||||
@@ -309,7 +313,8 @@ const SegmentationPanel = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
||||||
(segDisplay => segDisplay.loadError !== true));
|
segDisplay => segDisplay.loadError !== true
|
||||||
|
);
|
||||||
|
|
||||||
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
||||||
const {
|
const {
|
||||||
@@ -426,23 +431,43 @@ const SegmentationPanel = ({
|
|||||||
return enabledElements[activeIndex].element;
|
return enabledElements[activeIndex].element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => {
|
const onSegmentVisibilityChangeHandler = (
|
||||||
/** Get all labelmaps with this segmentNumber (overlapping segments) */
|
isVisible,
|
||||||
const { labelmaps3D } = getBrushStackState();
|
segmentNumber,
|
||||||
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
|
labelmap3D
|
||||||
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
|
) => {
|
||||||
segmentsOnLabelmap.includes(segmentNumber)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let segmentsHidden = [];
|
let segmentsHidden = [];
|
||||||
possibleLabelMaps3D.forEach(labelmap3D => {
|
if (labelmap3D.metadata.hasOverlapping) {
|
||||||
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
/** Get all labelmaps with this segmentNumber and that
|
||||||
|
* are from the same series (overlapping segments) */
|
||||||
|
const { labelmaps3D } = getBrushStackState();
|
||||||
|
|
||||||
segmentsHidden = [
|
const sameSeriesLabelMaps3D = labelmaps3D.filter(({ metadata }) => {
|
||||||
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
|
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 }));
|
setState(state => ({ ...state, segmentsHidden }));
|
||||||
|
|
||||||
@@ -509,6 +534,7 @@ const SegmentationPanel = ({
|
|||||||
label={segmentLabel}
|
label={segmentLabel}
|
||||||
index={segmentNumber}
|
index={segmentNumber}
|
||||||
color={color}
|
color={color}
|
||||||
|
labelmap3D={labelmap3D}
|
||||||
visible={!labelmap3D.segmentsHidden[segmentIndex]}
|
visible={!labelmap3D.segmentsHidden[segmentIndex]}
|
||||||
onVisibilityChange={onSegmentVisibilityChangeHandler}
|
onVisibilityChange={onSegmentVisibilityChangeHandler}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -64,8 +64,17 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
|||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
segDisplaySet.getSourceDisplaySet = function(studies, activateLabelMap = true, onDisplaySetLoadFailureHandler) {
|
segDisplaySet.getSourceDisplaySet = function(
|
||||||
return getSourceDisplaySet(studies, segDisplaySet, activateLabelMap, onDisplaySetLoadFailureHandler);
|
studies,
|
||||||
|
activateLabelMap = true,
|
||||||
|
onDisplaySetLoadFailureHandler
|
||||||
|
) {
|
||||||
|
return getSourceDisplaySet(
|
||||||
|
studies,
|
||||||
|
segDisplaySet,
|
||||||
|
activateLabelMap,
|
||||||
|
onDisplaySetLoadFailureHandler
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
segDisplaySet.load = async function(referencedDisplaySet, studies) {
|
segDisplaySet.load = async function(referencedDisplaySet, studies) {
|
||||||
@@ -98,6 +107,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
|||||||
if (labelmapBufferArray.length > 1) {
|
if (labelmapBufferArray.length > 1) {
|
||||||
let labelmapIndexes = [];
|
let labelmapIndexes = [];
|
||||||
for (let i = 0; i < labelmapBufferArray.length; ++i) {
|
for (let i = 0; i < labelmapBufferArray.length; ++i) {
|
||||||
|
segMetadata.segmentationSeriesInstanceUID =
|
||||||
|
segDisplaySet.SeriesInstanceUID;
|
||||||
|
segMetadata.hasOverlapping = true;
|
||||||
labelmapIndexes.push(
|
labelmapIndexes.push(
|
||||||
await loadSegmentation(
|
await loadSegmentation(
|
||||||
imageIds,
|
imageIds,
|
||||||
@@ -118,6 +130,9 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
|||||||
labelmapIndex = labelmapIndexes[0];
|
labelmapIndex = labelmapIndexes[0];
|
||||||
console.warn('Overlapping segments!');
|
console.warn('Overlapping segments!');
|
||||||
} else {
|
} else {
|
||||||
|
segMetadata.segmentationSeriesInstanceUID =
|
||||||
|
segDisplaySet.SeriesInstanceUID;
|
||||||
|
segMetadata.hasOverlapping = false;
|
||||||
labelmapIndex = await loadSegmentation(
|
labelmapIndex = await loadSegmentation(
|
||||||
imageIds,
|
imageIds,
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
@@ -135,10 +150,16 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _parseSeg(arrayBuffer, imageIds) {
|
function _parseSeg(arrayBuffer, imageIds) {
|
||||||
|
const skipOverlapping = false;
|
||||||
|
const tolerance = 1e-2;
|
||||||
|
const cornerstoneToolsVersion = 4;
|
||||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||||
imageIds,
|
imageIds,
|
||||||
arrayBuffer,
|
arrayBuffer,
|
||||||
cornerstone.metaData
|
cornerstone.metaData,
|
||||||
|
skipOverlapping,
|
||||||
|
tolerance,
|
||||||
|
cornerstoneToolsVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import OHIF from '@ohif/core';
|
||||||
|
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
const { studyMetadataManager } = OHIF.utils;
|
||||||
|
|
||||||
export default {
|
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 {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
label: 'Segmentations',
|
label: 'Segmentations',
|
||||||
target: 'segmentation-panel',
|
target: 'segmentation-panel',
|
||||||
isDisabled: studies => {
|
stateEvent: SegmentationPanelTabUpdatedEvent,
|
||||||
|
isDisabled: (studies, activeViewport) => {
|
||||||
if (!studies) {
|
if (!studies) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -98,6 +139,23 @@ export default {
|
|||||||
const series = study.series[j];
|
const series = study.series[j];
|
||||||
|
|
||||||
if (series.Modality === 'SEG') {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,16 @@ export default async function loadSegmentation(
|
|||||||
* without subscribing to external events.
|
* without subscribing to external events.
|
||||||
*/
|
*/
|
||||||
console.log('Segmentation loaded.');
|
console.log('Segmentation loaded.');
|
||||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
const event = new CustomEvent('extensiondicomsegmentationsegloaded', {
|
||||||
|
detail: {
|
||||||
|
imageIds,
|
||||||
|
segDisplaySet,
|
||||||
|
labelmapBuffer,
|
||||||
|
segMetadata,
|
||||||
|
segmentsOnFrame,
|
||||||
|
labelmapSegments,
|
||||||
|
},
|
||||||
|
});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
return labelmapIndex;
|
return labelmapIndex;
|
||||||
|
|||||||
@@ -3,6 +3,70 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.2.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.18...@ohif/extension-dicom-tag-browser@0.2.19) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-tag-browser",
|
"name": "@ohif/extension-dicom-tag-browser",
|
||||||
"version": "0.2.11",
|
"version": "0.2.19",
|
||||||
"description": "OHIF extension for checking DICOM headers.",
|
"description": "OHIF extension for checking DICOM headers.",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^2.6.0",
|
"@ohif/core": "^2.6.0",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"react": "^16.8.6"
|
"react": "^16.8.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"dicomweb-client": "^0.8.1",
|
"dicomweb-client": "^0.8.3",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"react-select": "^3.0.8"
|
"react-select": "^3.0.8"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
|||||||
activeDisplaySetInstanceUID,
|
activeDisplaySetInstanceUID,
|
||||||
setActiveDisplaySetInstanceUID,
|
setActiveDisplaySetInstanceUID,
|
||||||
] = useState(displaySetInstanceUID);
|
] = useState(displaySetInstanceUID);
|
||||||
const [activeInstance, setActiveInstance] = useState(0);
|
const [activeInstance, setActiveInstance] = useState(1);
|
||||||
const [tags, setTags] = useState([]);
|
const [tags, setTags] = useState([]);
|
||||||
const [meta, setMeta] = useState('');
|
const [meta, setMeta] = useState('');
|
||||||
const [instanceList, setInstanceList] = useState([]);
|
const [instanceList, setInstanceList] = useState([]);
|
||||||
@@ -51,20 +51,21 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
|||||||
description: displayDate,
|
description: displayDate,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
|
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
|
||||||
setActiveInstance(0);
|
setActiveInstance(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let metadata;
|
let metadata;
|
||||||
const isImageStack = activeDisplaySet instanceof ImageSet;
|
const isImageStack =
|
||||||
|
activeDisplaySet instanceof ImageSet &&
|
||||||
|
activeDisplaySet.isModalitySupported === true;
|
||||||
|
|
||||||
let selectedInstanceValue;
|
|
||||||
let instanceList;
|
let instanceList;
|
||||||
|
|
||||||
if (isImageStack) {
|
if (isImageStack) {
|
||||||
const { images } = activeDisplaySet;
|
const { images } = activeDisplaySet;
|
||||||
const image = images[activeInstance];
|
const image = images[activeInstance - 1];
|
||||||
|
|
||||||
instanceList = images.map((image, index) => {
|
instanceList = images.map((image, index) => {
|
||||||
const metadata = image.getData().metadata;
|
const metadata = image.getData().metadata;
|
||||||
@@ -91,7 +92,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
|||||||
setInstanceList(instanceList);
|
setInstanceList(instanceList);
|
||||||
setDisplaySetList(newDisplaySetList);
|
setDisplaySetList(newDisplaySetList);
|
||||||
setIsImageStack(isImageStack);
|
setIsImageStack(isImageStack);
|
||||||
}, [activeDisplaySetInstanceUID, activeInstance]);
|
}, [activeDisplaySetInstanceUID, activeInstance, displaySets]);
|
||||||
|
|
||||||
const selectedDisplaySetValue = displaySetList.find(
|
const selectedDisplaySetValue = displaySetList.find(
|
||||||
ds => ds.value === activeDisplaySetInstanceUID
|
ds => ds.value === activeDisplaySetInstanceUID
|
||||||
@@ -106,7 +107,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
|||||||
showValue
|
showValue
|
||||||
step={1}
|
step={1}
|
||||||
min={1}
|
min={1}
|
||||||
max={instanceList.length - 1}
|
max={instanceList.length}
|
||||||
value={activeInstance}
|
value={activeInstance}
|
||||||
valueRenderer={value => <p>Instance Number: {value}</p>}
|
valueRenderer={value => <p>Instance Number: {value}</p>}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
|
|||||||
@@ -3,6 +3,172 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.12.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.14...@ohif/extension-vtk@1.12.15) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.12.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.12.13...@ohif/extension-vtk@1.12.14) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* MPR metadata parsing for spacings ([#2517](https://github.com/OHIF/Viewers/issues/2517)) ([715dfc5](https://github.com/OHIF/Viewers/commit/715dfc556a3883de31d74012dcc99e431d91f57d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.11.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.13...@ohif/extension-vtk@1.11.14) (2021-06-03)
|
## [1.11.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.13...@ohif/extension-vtk@1.11.14) (2021-06-03)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-vtk
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.11.14",
|
"version": "1.12.15",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"@ohif/i18n": "^0.50.0",
|
"@ohif/i18n": "^0.50.0",
|
||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.6.1",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^4.1.0",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.11",
|
||||||
"i18next": "^17.0.3",
|
"i18next": "^17.0.3",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
@@ -50,14 +50,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"react-vtkjs-viewport": "^0.14.2"
|
"react-vtkjs-viewport": "^0.14.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^2.13.13",
|
"@ohif/core": "^2.16.11",
|
||||||
"@ohif/ui": "^1.9.2",
|
"@ohif/ui": "^1.10.6",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^4.1.0",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.11",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
"i18next": "^17.0.3",
|
"i18next": "^17.0.3",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onScroll: () => {},
|
onScroll: () => { },
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = 'OHIFVTKViewport';
|
static id = 'OHIFVTKViewport';
|
||||||
@@ -271,7 +271,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
volumeMapper.setSampleDistance(sampleDistance);
|
volumeMapper.setSampleDistance(sampleDistance);
|
||||||
|
|
||||||
// Be generous to surpress warnings, as the logging really hurts performance.
|
// Be generous to suppress warnings, as the logging really hurts performance.
|
||||||
// TODO: maybe we should auto adjust samples to 1000.
|
// TODO: maybe we should auto adjust samples to 1000.
|
||||||
volumeMapper.setMaximumSamplesPerRay(4000);
|
volumeMapper.setMaximumSamplesPerRay(4000);
|
||||||
|
|
||||||
@@ -398,7 +398,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
displaySet.displaySetInstanceUID !==
|
displaySet.displaySetInstanceUID !==
|
||||||
prevDisplaySet.displaySetInstanceUID ||
|
prevDisplaySet.displaySetInstanceUID ||
|
||||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
0 silly argv {
|
||||||
|
0 silly argv _: [ 'run' ],
|
||||||
|
0 silly argv stream: true,
|
||||||
|
0 silly argv lernaVersion: '3.18.3',
|
||||||
|
0 silly argv '$0': 'node_modules/.bin/lerna',
|
||||||
|
0 silly argv script: 'dev:viewer'
|
||||||
|
0 silly argv }
|
||||||
|
1 notice cli v3.18.3
|
||||||
|
2 verbose rootPath /home/davide/Development/OIHF/Viewers
|
||||||
|
3 info versioning independent
|
||||||
|
4 info Executing command in 1 package: "yarn run dev:viewer"
|
||||||
|
5 silly npmRunScript.stream [ 'dev:viewer', [], '@ohif/viewer' ]
|
||||||
|
6 silly getExecOpts /home/davide/Development/OIHF/Viewers/platform/viewer undefined
|
||||||
|
7 error MaxBufferError: stderr maxBuffer exceeded
|
||||||
|
7 error at PassThrough.<anonymous> (/home/davide/Development/OIHF/Viewers/node_modules/get-stream/index.js:41:19)
|
||||||
|
7 error at PassThrough.emit (events.js:412:35)
|
||||||
|
7 error at addChunk (internal/streams/readable.js:290:12)
|
||||||
|
7 error at readableAddChunk (internal/streams/readable.js:261:11)
|
||||||
|
7 error at PassThrough.Readable.push (internal/streams/readable.js:204:10)
|
||||||
|
7 error at PassThrough.Transform.push (internal/streams/transform.js:166:32)
|
||||||
|
7 error at PassThrough.afterTransform (internal/streams/transform.js:101:10)
|
||||||
|
7 error at PassThrough._transform (internal/streams/passthrough.js:46:3)
|
||||||
|
7 error at PassThrough.Transform._read (internal/streams/transform.js:205:10)
|
||||||
+4
-2
@@ -56,6 +56,7 @@
|
|||||||
"@babel/preset-env": "^7.5.0",
|
"@babel/preset-env": "^7.5.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@percy/cypress": "^2.2.0",
|
"@percy/cypress": "^2.2.0",
|
||||||
|
"@webpack-cli/serve": "^1.6.1",
|
||||||
"babel-eslint": "9.x",
|
"babel-eslint": "9.x",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-plugin-inline-react-svg": "1.1.0",
|
"babel-plugin-inline-react-svg": "1.1.0",
|
||||||
@@ -104,8 +105,8 @@
|
|||||||
"stylus-loader": "^3.0.2",
|
"stylus-loader": "^3.0.2",
|
||||||
"terser-webpack-plugin": "^2.1.0",
|
"terser-webpack-plugin": "^2.1.0",
|
||||||
"webpack": "^4.35.2",
|
"webpack": "^4.35.2",
|
||||||
"webpack-cli": "^3.3.5",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-dev-server": "^3.7.2",
|
"webpack-dev-server": "^4.7.3",
|
||||||
"webpack-hot-middleware": "^2.25.0",
|
"webpack-hot-middleware": "^2.25.0",
|
||||||
"webpack-merge": "^4.2.1",
|
"webpack-merge": "^4.2.1",
|
||||||
"workbox-webpack-plugin": "^5.0.0-beta.1",
|
"workbox-webpack-plugin": "^5.0.0-beta.1",
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"**/node-gyp": "8.4.1",
|
||||||
"**/@babel/runtime": "7.5.5"
|
"**/@babel/runtime": "7.5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,168 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.16.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.10...@ohif/core@2.16.11) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.16.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.16.9...@ohif/core@2.16.10) (2022-02-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* MPR metadata parsing for spacings ([#2517](https://github.com/OHIF/Viewers/issues/2517)) ([715dfc5](https://github.com/OHIF/Viewers/commit/715dfc556a3883de31d74012dcc99e431d91f57d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.13.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.12...@ohif/core@2.13.13) (2021-06-03)
|
## [2.13.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.12...@ohif/core@2.13.13) (2021-06-03)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/core
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "2.13.13",
|
"version": "2.16.11",
|
||||||
"description": "Generic business logic for web-based medical imaging applications",
|
"description": "Generic business logic for web-based medical imaging applications",
|
||||||
"author": "OHIF Core Team",
|
"author": "OHIF Core Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -31,20 +31,21 @@
|
|||||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.6.1",
|
||||||
"cornerstone-tools": "^4.20.1",
|
"cornerstone-tools": "^6.0.6",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^4.1.0",
|
||||||
"dicom-parser": "^1.8.3"
|
"dicom-parser": "^1.8.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
"dcmjs": "0.18.8",
|
"dcmjs": "0.19.4",
|
||||||
"dicomweb-client": "^0.8.1",
|
"dicomweb-client": "^0.8.3",
|
||||||
"immer": "6.0.2",
|
"immer": "6.0.2",
|
||||||
"isomorphic-base64": "^1.0.2",
|
"isomorphic-base64": "^1.0.2",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.merge": "^4.6.1",
|
"lodash.merge": "^4.6.1",
|
||||||
|
"mathjs": "^10.1.0",
|
||||||
"mousetrap": "^1.6.3",
|
"mousetrap": "^1.6.3",
|
||||||
"retry": "^0.12.0",
|
"retry": "^0.12.0",
|
||||||
"validate.js": "^0.12.0"
|
"validate.js": "^0.12.0"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
POINT: 'POINT',
|
||||||
|
MULTIPOINT: 'MULTIPOINT',
|
||||||
|
POLYLINE: 'POLYLINE',
|
||||||
|
CIRCLE: 'CIRCLE',
|
||||||
|
ELLIPSE: 'ELLIPSE',
|
||||||
|
POLYGON: 'POLYGON',
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const TOOL_NAMES = {
|
||||||
|
DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TOOL_NAMES;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export const CodeNameCodeSequenceValues = {
|
||||||
|
ImagingMeasurementReport: '126000',
|
||||||
|
ImageLibrary: '111028',
|
||||||
|
ImagingMeasurements: '126010',
|
||||||
|
MeasurementGroup: '125007',
|
||||||
|
ImageLibraryGroup: '126200',
|
||||||
|
TrackingUniqueIdentifier: '112040',
|
||||||
|
TrackingIdentifier: '112039',
|
||||||
|
Finding: '121071',
|
||||||
|
FindingSite: 'G-C0E3', // SRT
|
||||||
|
CornerstoneFreeText: 'CORNERSTONEFREETEXT', // CST4
|
||||||
|
Score: '246262008',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RELATIONSHIP_TYPE = {
|
||||||
|
INFERRED_FROM: 'INFERRED FROM',
|
||||||
|
SELECTED_FROM: 'SELECTED FROM',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CodingSchemeDesignators = {
|
||||||
|
SRT: 'SRT',
|
||||||
|
cornerstoneTools4: 'CST4',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
CodeNameCodeSequenceValues,
|
||||||
|
RELATIONSHIP_TYPE,
|
||||||
|
CodingSchemeDesignators,
|
||||||
|
};
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { ImageSet } from '../../classes';
|
||||||
|
import getMeasurements from './utils/getMeasurements';
|
||||||
|
import getReferencedImagesList from './utils/getReferencedImagesList';
|
||||||
|
import isRehydratable from './utils/isRehydratable';
|
||||||
|
import addMeasurement from './utils/addMeasurement';
|
||||||
|
|
||||||
|
const parseSCOORD3D = ({ servicesManager, displaySets }) => {
|
||||||
|
const { MeasurementService } = servicesManager.services;
|
||||||
|
|
||||||
|
const srDisplaySets = displaySets.filter(ds => ds.Modality === 'SR');
|
||||||
|
|
||||||
|
srDisplaySets.forEach(srDisplaySet => {
|
||||||
|
const firstInstance = srDisplaySet.metadata;
|
||||||
|
if (!firstInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ContentSequence } = firstInstance;
|
||||||
|
|
||||||
|
srDisplaySet.referencedImages = getReferencedImagesList(ContentSequence);
|
||||||
|
srDisplaySet.measurements = getMeasurements(ContentSequence, srDisplaySet);
|
||||||
|
const mappings = MeasurementService.getSourceMappings(
|
||||||
|
'CornerstoneTools',
|
||||||
|
'4'
|
||||||
|
);
|
||||||
|
|
||||||
|
srDisplaySet.isHydrated = false;
|
||||||
|
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
|
||||||
|
srDisplaySet.isLoaded = true;
|
||||||
|
|
||||||
|
const imageDisplaySets = displaySets.filter(
|
||||||
|
ds =>
|
||||||
|
ds.Modality !== 'SR' &&
|
||||||
|
ds.Modality !== 'SEG' &&
|
||||||
|
ds.Modality !== 'RTSTRUCT' &&
|
||||||
|
ds.Modality !== 'RTDOSE'
|
||||||
|
);
|
||||||
|
imageDisplaySets.forEach(imageDisplaySet => {
|
||||||
|
// Check currently added displaySets and add measurements if the sources exist.
|
||||||
|
checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, imageDisplaySet);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkIfCanAddMeasurementsToDisplaySet = (
|
||||||
|
srDisplaySet,
|
||||||
|
imageDisplaySet
|
||||||
|
) => {
|
||||||
|
let measurements = srDisplaySet.measurements;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for image sets.
|
||||||
|
* This also filters out _this_ displaySet, as it is not an image set.
|
||||||
|
*/
|
||||||
|
if (!(imageDisplaySet instanceof ImageSet)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sopClassUIDs, images } = imageDisplaySet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter measurements that references the correct sop class.
|
||||||
|
*/
|
||||||
|
measurements = measurements.filter(measurement => {
|
||||||
|
return measurement.coords.some(coord => {
|
||||||
|
if (coord.ReferencedSOPSequence === undefined) {
|
||||||
|
/** we miss the referenced information. We can compare the annotation SCOORD3D coordinates with
|
||||||
|
* the ImagePatientPosition of the frames. However (WARNING!!!),
|
||||||
|
* if more than a source series is present, this logic can find the wrong frame
|
||||||
|
* (i.e. two source series, with the same frameOfReferenceUID,
|
||||||
|
* that have each a frame with the same ImagePositionPatient of the annotation 3D coordinates)
|
||||||
|
*/
|
||||||
|
for (let i = 0; i < images.length; ++i) {
|
||||||
|
const imageMetadata = images[i].getData().metadata;
|
||||||
|
if (
|
||||||
|
imageMetadata.FrameOfReferenceUID !==
|
||||||
|
coord.ReferencedFrameOfReferenceSequence
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sliceNormal = [0, 0, 0];
|
||||||
|
const orientation = imageMetadata.ImageOrientationPatient;
|
||||||
|
sliceNormal[0] =
|
||||||
|
orientation[1] * orientation[5] - orientation[2] * orientation[4];
|
||||||
|
sliceNormal[1] =
|
||||||
|
orientation[2] * orientation[3] - orientation[0] * orientation[5];
|
||||||
|
sliceNormal[2] =
|
||||||
|
orientation[0] * orientation[4] - orientation[1] * orientation[3];
|
||||||
|
|
||||||
|
let distanceAlongNormal = 0;
|
||||||
|
for (let j = 0; j < 3; ++j) {
|
||||||
|
distanceAlongNormal +=
|
||||||
|
sliceNormal[j] * imageMetadata.ImagePositionPatient[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// assuming 1 mm tolerance
|
||||||
|
if (Math.abs(distanceAlongNormal - coord.GraphicData[2]) > 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
coord.ReferencedSOPSequence = {
|
||||||
|
ReferencedSOPClassUID: imageMetadata.SOPClassUID,
|
||||||
|
ReferencedSOPInstanceUID: imageMetadata.SOPInstanceUID,
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coord.ReferencedSOPSequence === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sopClassUIDs.includes(
|
||||||
|
coord.ReferencedSOPSequence.ReferencedSOPClassUID
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New display set doesn't have measurements that references the correct sop class.
|
||||||
|
*/
|
||||||
|
if (measurements.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIds = images.map(i => i.getImageId());
|
||||||
|
const SOPInstanceUIDs = images.map(i => i.SOPInstanceUID);
|
||||||
|
measurements.forEach(measurement => {
|
||||||
|
const { coords } = measurement;
|
||||||
|
|
||||||
|
coords.forEach(coord => {
|
||||||
|
if (coord.ReferencedSOPSequence !== undefined) {
|
||||||
|
const imageIndex = SOPInstanceUIDs.findIndex(
|
||||||
|
SOPInstanceUID =>
|
||||||
|
SOPInstanceUID ===
|
||||||
|
coord.ReferencedSOPSequence.ReferencedSOPInstanceUID
|
||||||
|
);
|
||||||
|
if (imageIndex > -1) {
|
||||||
|
const imageId = imageIds[imageIndex];
|
||||||
|
const imageMetadata = images[imageIndex].getData().metadata;
|
||||||
|
addMeasurement(
|
||||||
|
measurement,
|
||||||
|
imageId,
|
||||||
|
imageMetadata,
|
||||||
|
imageDisplaySet.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseSCOORD3D;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import csTools from 'cornerstone-tools';
|
||||||
|
import OHIF from '../../../';
|
||||||
|
|
||||||
|
/** Internal imports */
|
||||||
|
import TOOL_NAMES from '../constants/toolNames';
|
||||||
|
import getRenderableData from './getRenderableData';
|
||||||
|
|
||||||
|
const globalImageIdSpecificToolStateManager =
|
||||||
|
csTools.globalImageIdSpecificToolStateManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a measurement to a display set.
|
||||||
|
*
|
||||||
|
* @param {*} measurement
|
||||||
|
* @param {*} imageId
|
||||||
|
* @param {*} displaySetInstanceUID
|
||||||
|
*/
|
||||||
|
export default function addMeasurement(
|
||||||
|
measurement,
|
||||||
|
imageId,
|
||||||
|
imageMetadata,
|
||||||
|
displaySetInstanceUID
|
||||||
|
) {
|
||||||
|
// TODO -> Render rotated ellipse .
|
||||||
|
const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
|
||||||
|
|
||||||
|
const measurementData = {
|
||||||
|
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
||||||
|
renderableData: {},
|
||||||
|
labels: measurement.labels,
|
||||||
|
};
|
||||||
|
|
||||||
|
measurement.coords.forEach(coord => {
|
||||||
|
const { GraphicType, GraphicData, ValueType } = coord;
|
||||||
|
|
||||||
|
if (measurementData.renderableData[GraphicType] === undefined) {
|
||||||
|
measurementData.renderableData[GraphicType] = [];
|
||||||
|
}
|
||||||
|
measurementData.renderableData[GraphicType].push(
|
||||||
|
getRenderableData(GraphicType, GraphicData, ValueType, imageMetadata)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const toolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||||
|
|
||||||
|
if (toolState[imageId] === undefined) {
|
||||||
|
toolState[imageId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIdToolState = toolState[imageId];
|
||||||
|
|
||||||
|
// If we don't have tool state for this type of tool, add an empty object
|
||||||
|
if (imageIdToolState[toolName] === undefined) {
|
||||||
|
imageIdToolState[toolName] = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolData = imageIdToolState[toolName];
|
||||||
|
|
||||||
|
measurementData.description = `Read-only annotation`;
|
||||||
|
measurementData.isReadOnly = true;
|
||||||
|
toolData.data.push(measurementData);
|
||||||
|
|
||||||
|
addToMeasurementApi({ measurementData, toolName, imageId });
|
||||||
|
|
||||||
|
measurement.loaded = true;
|
||||||
|
measurement.imageId = imageId;
|
||||||
|
measurement.displaySetInstanceUID = displaySetInstanceUID;
|
||||||
|
|
||||||
|
// Remove the unneeded coord now its processed, but keep the SOPInstanceUID.
|
||||||
|
// NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame,
|
||||||
|
// It'd be super werid if it didn't anyway as a SCOORD.
|
||||||
|
measurement.ReferencedSOPInstanceUID =
|
||||||
|
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||||
|
|
||||||
|
return measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addToMeasurementApi = ({ measurementData, toolName, imageId }) => {
|
||||||
|
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||||
|
|
||||||
|
const toolType = toolName;
|
||||||
|
const collection = measurementApi.tools[toolType];
|
||||||
|
if (!collection) return;
|
||||||
|
if (!measurementData || measurementData.cancelled) return;
|
||||||
|
|
||||||
|
const imageAttributes = OHIF.measurements.getImageAttributes(null, imageId);
|
||||||
|
const measurement = Object.assign({}, measurementData, imageAttributes, {
|
||||||
|
lesionNamingNumber: measurementData.lesionNamingNumber,
|
||||||
|
userId: OHIF.user.getUserId(),
|
||||||
|
toolType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const addedMeasurement = measurementApi.addMeasurement(toolType, measurement);
|
||||||
|
Object.assign(measurementData, addedMeasurement);
|
||||||
|
|
||||||
|
const measurementLabel = OHIF.measurements.getLabel(measurementData);
|
||||||
|
if (measurementLabel) {
|
||||||
|
measurementData.labels = [measurementLabel];
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
const getCoordsFromSCOORDOrSCOORD3D = (graphicItem, displaySet) => {
|
||||||
|
const { ValueType, RelationshipType, GraphicType, GraphicData } = graphicItem;
|
||||||
|
|
||||||
|
// if (RelationshipType !== RELATIONSHIP_TYPE.INFERRED_FROM) {
|
||||||
|
// console.warn(
|
||||||
|
// `Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM."`
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const coords = { ValueType, GraphicType, GraphicData };
|
||||||
|
|
||||||
|
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
||||||
|
if (ValueType === 'SCOORD') {
|
||||||
|
const { ReferencedSOPSequence } = graphicItem.ContentSequence;
|
||||||
|
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
||||||
|
} else if (ValueType === 'SCOORD3D') {
|
||||||
|
if (graphicItem.ReferencedFrameOfReferenceUID) {
|
||||||
|
coords.ReferencedFrameOfReferenceSequence = graphicItem.ReferencedFrameOfReferenceUID;
|
||||||
|
} else if (graphicItem.ContentSequence) {
|
||||||
|
const {
|
||||||
|
ReferencedFrameOfReferenceSequence,
|
||||||
|
} = graphicItem.ContentSequence;
|
||||||
|
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coords;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCoordsFromSCOORDOrSCOORD3D;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
const getLabelFromMeasuredValueSequence = (
|
||||||
|
ConceptNameCodeSequence,
|
||||||
|
MeasuredValueSequence
|
||||||
|
) => {
|
||||||
|
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||||
|
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||||
|
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||||
|
|
||||||
|
const formatedNumericValue = NumericValue
|
||||||
|
? Number(NumericValue).toFixed(1)
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: CodeMeaning,
|
||||||
|
value: `${formatedNumericValue} ${CodeValue}`,
|
||||||
|
}; // E.g. Long Axis: 31.0 mm
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getLabelFromMeasuredValueSequence;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { CodeNameCodeSequenceValues } from '../enums';
|
||||||
|
import getSequenceAsArray from './getSequenceAsArray';
|
||||||
|
import getMergedContentSequencesByTrackingUniqueIdentifiers from './getMergedContentSequencesByTrackingUniqueIdentifiers';
|
||||||
|
import processMeasurement from './processMeasurement';
|
||||||
|
|
||||||
|
const getMeasurements = (
|
||||||
|
ImagingMeasurementReportContentSequence,
|
||||||
|
displaySet
|
||||||
|
) => {
|
||||||
|
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.ImagingMeasurements
|
||||||
|
);
|
||||||
|
|
||||||
|
const MeasurementGroups = getSequenceAsArray(
|
||||||
|
ImagingMeasurements.ContentSequence
|
||||||
|
).filter(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.MeasurementGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergedContentSequencesByTrackingUniqueIdentifiers = getMergedContentSequencesByTrackingUniqueIdentifiers(
|
||||||
|
MeasurementGroups
|
||||||
|
);
|
||||||
|
|
||||||
|
let measurements = [];
|
||||||
|
|
||||||
|
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||||
|
trackingUniqueIdentifier => {
|
||||||
|
const mergedContentSequence =
|
||||||
|
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||||
|
trackingUniqueIdentifier
|
||||||
|
];
|
||||||
|
|
||||||
|
const measurement = processMeasurement(mergedContentSequence, displaySet);
|
||||||
|
if (measurement) {
|
||||||
|
measurements.push(measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return measurements;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getMeasurements;
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import getSequenceAsArray from './getSequenceAsArray';
|
||||||
|
import { CodeNameCodeSequenceValues } from '../enums';
|
||||||
|
|
||||||
|
const getMergedContentSequencesByTrackingUniqueIdentifiers = MeasurementGroups => {
|
||||||
|
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||||
|
|
||||||
|
MeasurementGroups.forEach(MeasurementGroup => {
|
||||||
|
const ContentSequence = getSequenceAsArray(
|
||||||
|
MeasurementGroup.ContentSequence
|
||||||
|
);
|
||||||
|
|
||||||
|
const TrackingUniqueIdentifierItem = ContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!TrackingUniqueIdentifierItem) {
|
||||||
|
console.warn(
|
||||||
|
'No Tracking Unique Identifier, skipping ambiguous measurement.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID;
|
||||||
|
|
||||||
|
if (
|
||||||
|
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||||
|
trackingUniqueIdentifier
|
||||||
|
] === undefined
|
||||||
|
) {
|
||||||
|
// Add the full ContentSequence
|
||||||
|
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||||
|
trackingUniqueIdentifier
|
||||||
|
] = [...ContentSequence];
|
||||||
|
} else {
|
||||||
|
// Add the ContentSequence minus the tracking identifier, as we have this
|
||||||
|
// Information in the merged ContentSequence anyway.
|
||||||
|
ContentSequence.forEach(item => {
|
||||||
|
if (
|
||||||
|
item.ConceptNameCodeSequence.CodeValue !==
|
||||||
|
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||||
|
) {
|
||||||
|
mergedContentSequencesByTrackingUniqueIdentifiers[
|
||||||
|
trackingUniqueIdentifier
|
||||||
|
].push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getMergedContentSequencesByTrackingUniqueIdentifiers;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import getSequenceAsArray from './getSequenceAsArray';
|
||||||
|
import { CodeNameCodeSequenceValues } from '../enums';
|
||||||
|
|
||||||
|
const getReferencedImagesList = ImagingMeasurementReportContentSequence => {
|
||||||
|
const referencedImages = [];
|
||||||
|
|
||||||
|
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.ImageLibrary
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ImageLibrary.ContentSequence) {
|
||||||
|
return referencedImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageLibraryGroup = getSequenceAsArray(
|
||||||
|
ImageLibrary.ContentSequence
|
||||||
|
).find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
|
||||||
|
const { ReferencedSOPSequence } = item;
|
||||||
|
if (ReferencedSOPSequence) {
|
||||||
|
const {
|
||||||
|
ReferencedSOPClassUID,
|
||||||
|
ReferencedSOPInstanceUID,
|
||||||
|
} = ReferencedSOPSequence;
|
||||||
|
|
||||||
|
referencedImages.push({
|
||||||
|
ReferencedSOPClassUID,
|
||||||
|
ReferencedSOPInstanceUID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return referencedImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getReferencedImagesList;
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import csMath from 'cornerstone-math';
|
||||||
|
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||||
|
import { inv } from 'mathjs';
|
||||||
|
|
||||||
|
const getRenderableData = (
|
||||||
|
GraphicType,
|
||||||
|
GraphicData,
|
||||||
|
ValueType,
|
||||||
|
imageMetadata
|
||||||
|
) => {
|
||||||
|
let renderableData;
|
||||||
|
|
||||||
|
const orientation = imageMetadata.ImageOrientationPatient;
|
||||||
|
const position = imageMetadata.ImagePositionPatient;
|
||||||
|
const pixelSpacing = imageMetadata.PixelSpacing;
|
||||||
|
const sliceSpacing = imageMetadata.SliceThickness
|
||||||
|
? imageMetadata.SliceThickness
|
||||||
|
: 1;
|
||||||
|
// https://nipy.org/nibabel/dicom/dicom_orientation.html
|
||||||
|
const M = [
|
||||||
|
[
|
||||||
|
orientation[0] * pixelSpacing[0],
|
||||||
|
orientation[3] * pixelSpacing[1],
|
||||||
|
sliceSpacing,
|
||||||
|
position[0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
orientation[1] * pixelSpacing[0],
|
||||||
|
orientation[4] * pixelSpacing[1],
|
||||||
|
sliceSpacing,
|
||||||
|
position[1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
orientation[2] * pixelSpacing[0],
|
||||||
|
orientation[5] * pixelSpacing[1],
|
||||||
|
sliceSpacing,
|
||||||
|
position[2],
|
||||||
|
],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
// we need to go from 3D to pixel (cornerstone2D works in pixel coordinates),
|
||||||
|
// we take the inverse.
|
||||||
|
const M1 = inv(M);
|
||||||
|
|
||||||
|
const worldToIJK = (point, M1) => {
|
||||||
|
const worldPoint = {
|
||||||
|
x:
|
||||||
|
M1[0][0] * point.x + M1[0][1] * point.y + M1[0][2] * point.z + M1[0][3],
|
||||||
|
y:
|
||||||
|
M1[1][0] * point.x + M1[1][1] * point.y + M1[1][2] * point.z + M1[1][3],
|
||||||
|
z:
|
||||||
|
M1[2][0] * point.x + M1[2][1] * point.y + M1[2][2] * point.z + M1[2][3],
|
||||||
|
};
|
||||||
|
return worldPoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://dicom.innolitics.com/ciods/procedure-log/sr-document-content/00700023
|
||||||
|
switch (GraphicType) {
|
||||||
|
case SCOORD_TYPES.POINT:
|
||||||
|
renderableData = [];
|
||||||
|
|
||||||
|
if (ValueType === 'SCOORD3D') {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||||
|
const point = {
|
||||||
|
x: GraphicData[i],
|
||||||
|
y: GraphicData[i + 1],
|
||||||
|
z: GraphicData[i + 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
renderableData.push(worldToIJK(point, M1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||||
|
renderableData.push({ x: GraphicData[i], y: GraphicData[i + 1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.MULTIPOINT:
|
||||||
|
renderableData = [];
|
||||||
|
|
||||||
|
if (ValueType === 'SCOORD3D') {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||||
|
const point = {
|
||||||
|
x: GraphicData[i],
|
||||||
|
y: GraphicData[i + 1],
|
||||||
|
z: GraphicData[i + 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
renderableData.push(worldToIJK(point, M1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||||
|
renderableData.push({ x: GraphicData[i], y: GraphicData[i + 1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.POLYLINE:
|
||||||
|
renderableData = [];
|
||||||
|
|
||||||
|
if (ValueType === 'SCOORD3D') {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||||
|
const point = {
|
||||||
|
x: GraphicData[i],
|
||||||
|
y: GraphicData[i + 1],
|
||||||
|
z: GraphicData[i + 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
renderableData.push(worldToIJK(point, M1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||||
|
renderableData.push({ x: GraphicData[i], y: GraphicData[i + 1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.POLYGON:
|
||||||
|
// this is only scoord3d
|
||||||
|
renderableData = [];
|
||||||
|
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||||
|
const point = {
|
||||||
|
x: GraphicData[i],
|
||||||
|
y: GraphicData[i + 1],
|
||||||
|
z: GraphicData[i + 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
renderableData.push(worldToIJK(point, M1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SCOORD_TYPES.CIRCLE: {
|
||||||
|
// this is only scoord
|
||||||
|
const center = { x: GraphicData[0], y: GraphicData[1] };
|
||||||
|
const onPerimeter = { x: GraphicData[2], y: GraphicData[3] };
|
||||||
|
|
||||||
|
const radius = csMath.point.distance(center, onPerimeter);
|
||||||
|
|
||||||
|
renderableData = {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SCOORD_TYPES.ELLIPSE: {
|
||||||
|
console.warn('ROTATED ELLIPSE NOT YET SUPPORTED!');
|
||||||
|
// To Do: scoord3d ellips, need data for testing
|
||||||
|
const majorAxis = [
|
||||||
|
{ x: GraphicData[0], y: GraphicData[1] },
|
||||||
|
{ x: GraphicData[2], y: GraphicData[3] },
|
||||||
|
];
|
||||||
|
const minorAxis = [
|
||||||
|
{ x: GraphicData[4], y: GraphicData[5] },
|
||||||
|
{ x: GraphicData[6], y: GraphicData[7] },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Calculate two opposite corners of box defined by two axes.
|
||||||
|
|
||||||
|
const minorAxisLength = csMath.point.distance(minorAxis[0], minorAxis[1]);
|
||||||
|
|
||||||
|
const minorAxisDirection = {
|
||||||
|
x: (minorAxis[1].x - minorAxis[0].x) / minorAxisLength,
|
||||||
|
y: (minorAxis[1].y - minorAxis[0].y) / minorAxisLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
const halfMinorAxisLength = minorAxisLength / 2;
|
||||||
|
|
||||||
|
// First end point of major axis + half minor axis vector
|
||||||
|
const corner1 = {
|
||||||
|
x: majorAxis[0].x + minorAxisDirection.x * halfMinorAxisLength,
|
||||||
|
y: majorAxis[0].y + minorAxisDirection.y * halfMinorAxisLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Second end point of major axis - half of minor axis vector
|
||||||
|
const corner2 = {
|
||||||
|
x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength,
|
||||||
|
y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderableData = {
|
||||||
|
corner1,
|
||||||
|
corner2,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderableData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRenderableData;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
const getSequenceAsArray = sequence =>
|
||||||
|
Array.isArray(sequence) ? sequence : [sequence];
|
||||||
|
|
||||||
|
export default getSequenceAsArray;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { adapters } from 'dcmjs';
|
||||||
|
|
||||||
|
const cornerstoneAdapters = adapters.Cornerstone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given `displaySet`can be rehydrated into the `MeasurementService`.
|
||||||
|
*
|
||||||
|
* @param {object} displaySet The SR `displaySet` to check.
|
||||||
|
* @param {object[]} mappings The CornerstoneTools 4 mappings to the `MeasurementService`.
|
||||||
|
* @returns {boolean} True if the SR can be rehydrated into the `MeasurementService`.
|
||||||
|
*/
|
||||||
|
export default function isRehydratable(displaySet, mappings) {
|
||||||
|
if (!mappings || !mappings.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappingDefinitions = mappings.map(m => m.definition);
|
||||||
|
const { measurements } = displaySet;
|
||||||
|
|
||||||
|
const adapterKeys = Object.keys(cornerstoneAdapters).filter(
|
||||||
|
adapterKey =>
|
||||||
|
typeof cornerstoneAdapters[adapterKey]
|
||||||
|
.isValidCornerstoneTrackingIdentifier === 'function'
|
||||||
|
);
|
||||||
|
|
||||||
|
const adapters = [];
|
||||||
|
|
||||||
|
adapterKeys.forEach(key => {
|
||||||
|
if (mappingDefinitions.includes(key)) {
|
||||||
|
// Must have both a dcmjs adapter and a MeasurementService
|
||||||
|
// Definition in order to be a candidate for import.
|
||||||
|
adapters.push(cornerstoneAdapters[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < measurements.length; i++) {
|
||||||
|
const TrackingIdentifier = measurements[i].TrackingIdentifier;
|
||||||
|
const hydratable = adapters.some(adapter =>
|
||||||
|
adapter.isValidCornerstoneTrackingIdentifier(TrackingIdentifier)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hydratable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import processTID1410Measurement from './processTID1410Measurement';
|
||||||
|
import processNonGeometricallyDefinedMeasurement from './processNonGeometricallyDefinedMeasurement';
|
||||||
|
|
||||||
|
const processMeasurement = (mergedContentSequence, displaySet) => {
|
||||||
|
if (
|
||||||
|
mergedContentSequence.some(
|
||||||
|
group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return processTID1410Measurement(mergedContentSequence, displaySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default processMeasurement;
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
import getLabelFromMeasuredValueSequence from './getLabelFromMeasuredValueSequence';
|
||||||
|
import getCoordsFromSCOORDOrSCOORD3D from './getCoordsFromSCOORDOrSCOORD3D';
|
||||||
|
import { CodeNameCodeSequenceValues, CodingSchemeDesignators } from '../enums';
|
||||||
|
|
||||||
|
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
||||||
|
|
||||||
|
const processNonGeometricallyDefinedMeasurement = mergedContentSequence => {
|
||||||
|
const NUMContentItems = mergedContentSequence.filter(
|
||||||
|
group => group.ValueType === 'NUM'
|
||||||
|
);
|
||||||
|
|
||||||
|
const UIDREFContentItem = mergedContentSequence.find(
|
||||||
|
group => group.ValueType === 'UIDREF'
|
||||||
|
);
|
||||||
|
|
||||||
|
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.TrackingIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
const Finding = mergedContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.Finding
|
||||||
|
);
|
||||||
|
|
||||||
|
const FindingSites = mergedContentSequence.filter(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodingSchemeDesignator ===
|
||||||
|
CodingSchemeDesignators.SRT &&
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.FindingSite
|
||||||
|
);
|
||||||
|
|
||||||
|
const measurement = {
|
||||||
|
loaded: false,
|
||||||
|
labels: [],
|
||||||
|
coords: [],
|
||||||
|
TrackingUniqueIdentifier: UIDREFContentItem.UID,
|
||||||
|
TrackingIdentifier: TrackingIdentifierContentItem.TextValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
Finding &&
|
||||||
|
Finding.ConceptCodeSequence.CodingSchemeDesignator ===
|
||||||
|
CodingSchemeDesignators.cornerstoneTools4 &&
|
||||||
|
Finding.ConceptCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||||
|
) {
|
||||||
|
measurement.labels.push({
|
||||||
|
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||||
|
value: Finding.ConceptCodeSequence.CodeMeaning,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO -> Eventually hopefully support SNOMED or some proper code library, just free text for now.
|
||||||
|
if (FindingSites.length) {
|
||||||
|
const cornerstoneFreeTextFindingSite = FindingSites.find(
|
||||||
|
FindingSite =>
|
||||||
|
FindingSite.ConceptCodeSequence.CodingSchemeDesignator ===
|
||||||
|
CodingSchemeDesignators.cornerstoneTools4 &&
|
||||||
|
FindingSite.ConceptCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cornerstoneFreeTextFindingSite) {
|
||||||
|
measurement.labels.push({
|
||||||
|
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||||
|
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NUMContentItems.forEach(item => {
|
||||||
|
const {
|
||||||
|
ConceptNameCodeSequence,
|
||||||
|
ContentSequence,
|
||||||
|
MeasuredValueSequence,
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
if (!ContentSequence) {
|
||||||
|
console.warn(`Graphic ${ContentSequence} missing, skipping annotation.`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ValueType } = ContentSequence;
|
||||||
|
|
||||||
|
if (!ValueType === 'SCOORD' && !ValueType === 'SCOORD3D') {
|
||||||
|
console.warn(
|
||||||
|
`Graphic ${ValueType} not currently supported, skipping annotation.`
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const coords = getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
||||||
|
|
||||||
|
if (coords) {
|
||||||
|
measurement.coords.push(coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MeasuredValueSequence) {
|
||||||
|
measurement.labels.push(
|
||||||
|
getLabelFromMeasuredValueSequence(
|
||||||
|
ConceptNameCodeSequence,
|
||||||
|
MeasuredValueSequence
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return measurement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default processNonGeometricallyDefinedMeasurement;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import getLabelFromMeasuredValueSequence from './getLabelFromMeasuredValueSequence';
|
||||||
|
import getCoordsFromSCOORDOrSCOORD3D from './getCoordsFromSCOORDOrSCOORD3D';
|
||||||
|
import { RELATIONSHIP_TYPE, CodeNameCodeSequenceValues } from '../enums';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TID 1410 Planar ROI Measurements and Qualitative Evaluations.
|
||||||
|
*
|
||||||
|
* @param {*} mergedContentSequence
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const processTID1410Measurement = (mergedContentSequence, displaySet) => {
|
||||||
|
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||||
|
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||||
|
|
||||||
|
const graphicItem = mergedContentSequence.find(
|
||||||
|
group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!graphicItem) {
|
||||||
|
console.warn(
|
||||||
|
`graphic ValueType ${graphicItem.ValueType} not currently supported, skipping annotation.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UIDREFContentItem = mergedContentSequence.find(
|
||||||
|
group => group.ValueType === 'UIDREF'
|
||||||
|
);
|
||||||
|
|
||||||
|
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||||
|
item =>
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.TrackingIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
|
const NUMContentItems = mergedContentSequence.filter(
|
||||||
|
group => group.ValueType === 'NUM'
|
||||||
|
);
|
||||||
|
|
||||||
|
const measurement = {
|
||||||
|
loaded: false,
|
||||||
|
labels: [],
|
||||||
|
coords: [getCoordsFromSCOORDOrSCOORD3D(graphicItem, displaySet)],
|
||||||
|
TrackingUniqueIdentifier: UIDREFContentItem.UID,
|
||||||
|
TrackingIdentifier: TrackingIdentifierContentItem.TextValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
NUMContentItems.forEach(item => {
|
||||||
|
const {
|
||||||
|
ConceptNameCodeSequence,
|
||||||
|
ContentSequence,
|
||||||
|
MeasuredValueSequence,
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.ConceptNameCodeSequence.CodeValue ===
|
||||||
|
CodeNameCodeSequenceValues.Score
|
||||||
|
) {
|
||||||
|
ContentSequence.forEach(item => {
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
RELATIONSHIP_TYPE.SELECTED_FROM,
|
||||||
|
RELATIONSHIP_TYPE.INFERRED_FROM,
|
||||||
|
].includes(item.RelationshipType)
|
||||||
|
) {
|
||||||
|
if (item.ReferencedSOPSequence) {
|
||||||
|
measurement.coords.forEach(coord => {
|
||||||
|
coord.ReferencedSOPSequence = item.ReferencedSOPSequence;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MeasuredValueSequence) {
|
||||||
|
measurement.labels.push(
|
||||||
|
getLabelFromMeasuredValueSequence(
|
||||||
|
ConceptNameCodeSequence,
|
||||||
|
MeasuredValueSequence
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return measurement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default processTID1410Measurement;
|
||||||
@@ -19,9 +19,10 @@ import findMostRecentStructuredReport from './utils/findMostRecentStructuredRepo
|
|||||||
* Function to be registered into MeasurementAPI to retrieve measurements from DICOM Structured Reports
|
* Function to be registered into MeasurementAPI to retrieve measurements from DICOM Structured Reports
|
||||||
*
|
*
|
||||||
* @param {serverType} server
|
* @param {serverType} server
|
||||||
|
* @param {object} external
|
||||||
* @returns {Promise} Should resolve with OHIF measurementData object
|
* @returns {Promise} Should resolve with OHIF measurementData object
|
||||||
*/
|
*/
|
||||||
const retrieveMeasurements = server => {
|
const retrieveMeasurements = (server, external = {}) => {
|
||||||
log.info('[DICOMSR] retrieveMeasurements');
|
log.info('[DICOMSR] retrieveMeasurements');
|
||||||
|
|
||||||
if (!server || server.type !== 'dicomWeb') {
|
if (!server || server.type !== 'dicomWeb') {
|
||||||
@@ -36,7 +37,7 @@ const retrieveMeasurements = server => {
|
|||||||
|
|
||||||
if (!latestSeries) return Promise.resolve({});
|
if (!latestSeries) return Promise.resolve({});
|
||||||
|
|
||||||
return retrieveMeasurementFromSR(latestSeries, studies, serverUrl);
|
return retrieveMeasurementFromSR(latestSeries, studies, serverUrl, external);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,9 +17,15 @@ const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
|
|||||||
* @param {Array} series - List of all series metaData loaded
|
* @param {Array} series - List of all series metaData loaded
|
||||||
* @param {Array} studies - List of all studies metaData loaded
|
* @param {Array} studies - List of all studies metaData loaded
|
||||||
* @param {string} serverUrl - Server URL to be used on request
|
* @param {string} serverUrl - Server URL to be used on request
|
||||||
|
* @param {object} external
|
||||||
* @returns {Object} MeasurementData
|
* @returns {Object} MeasurementData
|
||||||
*/
|
*/
|
||||||
const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
|
const retrieveMeasurementFromSR = async (
|
||||||
|
series,
|
||||||
|
studies,
|
||||||
|
serverUrl,
|
||||||
|
external
|
||||||
|
) => {
|
||||||
const config = {
|
const config = {
|
||||||
url: serverUrl,
|
url: serverUrl,
|
||||||
headers: DICOMWeb.getAuthorizationHeader(),
|
headers: DICOMWeb.getAuthorizationHeader(),
|
||||||
@@ -40,7 +46,8 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
|
|||||||
const displaySets = getAllDisplaySets(studies);
|
const displaySets = getAllDisplaySets(studies);
|
||||||
const measurementsData = parseDicomStructuredReport(
|
const measurementsData = parseDicomStructuredReport(
|
||||||
part10SRArrayBuffer,
|
part10SRArrayBuffer,
|
||||||
displaySets
|
displaySets,
|
||||||
|
external
|
||||||
);
|
);
|
||||||
|
|
||||||
return measurementsData;
|
return measurementsData;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
|
import classes from '../classes';
|
||||||
|
import parseSCOORD3D from './SCOORD3D/parseSCOORD3D';
|
||||||
|
|
||||||
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
|
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
|
||||||
|
|
||||||
|
const { LogManager } = classes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
|
* 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
|
* measurementData format is a viewer specific format to be stored into the redux and consumed by other components
|
||||||
@@ -9,20 +13,40 @@ import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBy
|
|||||||
*
|
*
|
||||||
* @param {ArrayBuffer} part10SRArrayBuffer
|
* @param {ArrayBuffer} part10SRArrayBuffer
|
||||||
* @param {Array} displaySets
|
* @param {Array} displaySets
|
||||||
|
* @param {object} external
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
const parseDicomStructuredReport = (
|
||||||
// Get the dicom data as an Object
|
part10SRArrayBuffer,
|
||||||
|
displaySets,
|
||||||
|
external
|
||||||
|
) => {
|
||||||
|
if (external && external.servicesManager) {
|
||||||
|
parseSCOORD3D({ servicesManager: external.servicesManager, displaySets });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the dicom data as an Object
|
||||||
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
||||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
dicomData.dict
|
dicomData.dict
|
||||||
);
|
);
|
||||||
|
|
||||||
const { MeasurementReport } = dcmjs.adapters.Cornerstone;
|
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 = {};
|
const measurementData = {};
|
||||||
let measurementNumber = 0;
|
let measurementNumber = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const isStructuredReportSeries = series => {
|
|||||||
const supportedSopClassUIDs = [
|
const supportedSopClassUIDs = [
|
||||||
'1.2.840.10008.5.1.4.1.1.88.22',
|
'1.2.840.10008.5.1.4.1.1.88.22',
|
||||||
'1.2.840.10008.5.1.4.1.1.11.1',
|
'1.2.840.10008.5.1.4.1.1.11.1',
|
||||||
|
'1.2.840.10008.5.1.4.1.1.88.34', // COMPREHENSIVE_3D_SR
|
||||||
];
|
];
|
||||||
|
|
||||||
const firstInstance = series.getFirstInstance();
|
const firstInstance = series.getFirstInstance();
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -231,26 +231,26 @@ class MetadataProvider {
|
|||||||
let columnCosines;
|
let columnCosines;
|
||||||
|
|
||||||
if (PixelSpacing) {
|
if (PixelSpacing) {
|
||||||
rowPixelSpacing = PixelSpacing[0];
|
rowPixelSpacing = validNumber(PixelSpacing[0]);
|
||||||
columnPixelSpacing = PixelSpacing[1];
|
columnPixelSpacing = validNumber(PixelSpacing[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImageOrientationPatient) {
|
if (ImageOrientationPatient) {
|
||||||
rowCosines = ImageOrientationPatient.slice(0, 3);
|
rowCosines = validNumber(ImageOrientationPatient.slice(0, 3));
|
||||||
columnCosines = ImageOrientationPatient.slice(3, 6);
|
columnCosines = validNumber(ImageOrientationPatient.slice(3, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
frameOfReferenceUID: instance.FrameOfReferenceUID,
|
frameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||||
rows: instance.Rows,
|
rows: instance.Rows,
|
||||||
columns: instance.Columns,
|
columns: instance.Columns,
|
||||||
imageOrientationPatient: ImageOrientationPatient,
|
imageOrientationPatient: validNumber(ImageOrientationPatient),
|
||||||
rowCosines,
|
rowCosines,
|
||||||
columnCosines,
|
columnCosines,
|
||||||
imagePositionPatient: instance.ImagePositionPatient,
|
imagePositionPatient: validNumber(instance.ImagePositionPatient),
|
||||||
sliceThickness: instance.SliceThickness,
|
sliceThickness: validNumber(instance.SliceThickness),
|
||||||
sliceLocation: instance.SliceLocation,
|
sliceLocation: validNumber(instance.SliceLocation),
|
||||||
pixelSpacing: PixelSpacing,
|
pixelSpacing: validNumber(PixelSpacing),
|
||||||
rowPixelSpacing,
|
rowPixelSpacing,
|
||||||
columnPixelSpacing,
|
columnPixelSpacing,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
import {
|
import debounce from 'lodash.debounce';
|
||||||
clearStudyLoadingProgress,
|
|
||||||
setStudyLoadingProgress,
|
|
||||||
} from '../redux/actions';
|
|
||||||
import StackManager from '../utils/StackManager';
|
import StackManager from '../utils/StackManager';
|
||||||
|
import { StudyPrefetcher } from './StudyPrefetcher';
|
||||||
|
|
||||||
class BaseLoadingListener {
|
class BaseLoadingListener {
|
||||||
constructor(stack, options = {}) {
|
constructor(stack, options = {}) {
|
||||||
this.id = BaseLoadingListener.getNewId();
|
this.id = BaseLoadingListener.getNewId();
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
this.startListening();
|
|
||||||
this.statsItemsLimit = options.statsItemsLimit || 2;
|
this.statsItemsLimit = options.statsItemsLimit || 2;
|
||||||
this.stats = {
|
this.stats = {
|
||||||
items: [],
|
items: [],
|
||||||
@@ -60,6 +58,9 @@ class BaseLoadingListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getProgressId() {
|
_getProgressId() {
|
||||||
|
/**
|
||||||
|
* TODO: The id key should be configurable.
|
||||||
|
*/
|
||||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||||
return 'StackProgress:' + displaySetInstanceUID;
|
return 'StackProgress:' + displaySetInstanceUID;
|
||||||
}
|
}
|
||||||
@@ -96,11 +97,18 @@ class BaseLoadingListener {
|
|||||||
class DICOMFileLoadingListener extends BaseLoadingListener {
|
class DICOMFileLoadingListener extends BaseLoadingListener {
|
||||||
constructor(stack, options) {
|
constructor(stack, options) {
|
||||||
super(stack, options);
|
super(stack, options);
|
||||||
|
|
||||||
|
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandler.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
this._dataSetUrl = this._getDataSetUrl(stack);
|
this._dataSetUrl = this._getDataSetUrl(stack);
|
||||||
this._lastLoaded = 0;
|
this._lastLoaded = 0;
|
||||||
|
|
||||||
// Check how many instances has already been download (cached)
|
// Check how many instances has already been download (cached)
|
||||||
this._checkCachedData();
|
this._checkCachedData();
|
||||||
|
|
||||||
|
this.startListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkCachedData() {
|
_checkCachedData() {
|
||||||
@@ -127,10 +135,6 @@ class DICOMFileLoadingListener extends BaseLoadingListener {
|
|||||||
startListening() {
|
startListening() {
|
||||||
const imageLoadProgressEventName = this._getImageLoadProgressEventName();
|
const imageLoadProgressEventName = this._getImageLoadProgressEventName();
|
||||||
|
|
||||||
this.imageLoadProgressEventHandler = this._imageLoadProgressEventHandle.bind(
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
|
|
||||||
cornerstone.events.addEventListener(
|
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 {
|
class StackLoadingListener extends BaseLoadingListener {
|
||||||
constructor(stack, options = {}) {
|
constructor(stack, options = {}) {
|
||||||
options.statsItemsLimit = 20;
|
options.statsItemsLimit = 20;
|
||||||
|
|
||||||
super(stack, options);
|
super(stack, options);
|
||||||
|
|
||||||
|
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
|
||||||
|
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
|
this.imageDataMap = this._convertImageIdsArrayToMap(stack.imageIds);
|
||||||
this.framesStatus = this._createArray(stack.imageIds.length, false);
|
this.framesStatus = this._createArray(stack.imageIds.length, false);
|
||||||
this.loadedCount = 0;
|
this.loadedCount = 0;
|
||||||
|
|
||||||
// Check how many instances has already been download (cached)
|
// 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) {
|
_convertImageIdsArrayToMap(imageIds) {
|
||||||
@@ -238,18 +282,37 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkCachedData() {
|
/**
|
||||||
// const imageIds = this.stack.imageIds;
|
* Check if image id is cached in cornerstone.
|
||||||
// TODO: No way to check status of Promise.
|
*
|
||||||
/*for(let i = 0; i < imageIds.length; i++) {
|
* @param {string} imageId
|
||||||
const imageId = imageIds[i];
|
* @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')) {
|
for (let i = 0; i < imageIds.length; i++) {
|
||||||
this._updateFrameStatus(imageId, true);
|
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() {
|
_getImageLoadedEventName() {
|
||||||
@@ -272,11 +335,6 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
const imageLoadedEventName = this._getImageLoadedEventName();
|
const imageLoadedEventName = this._getImageLoadedEventName();
|
||||||
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
|
const imageCachePromiseRemovedEventName = this._getImageCachePromiseRemoveEventName();
|
||||||
|
|
||||||
this.imageLoadedEventHandler = this._imageLoadedEventHandler.bind(this);
|
|
||||||
this.imageCachePromiseRemovedEventHandler = this._imageCachePromiseRemovedEventHandler.bind(
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
|
|
||||||
cornerstone.events.addEventListener(
|
cornerstone.events.addEventListener(
|
||||||
@@ -303,7 +361,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateFrameStatus(imageId, loaded) {
|
_updateFrameStatus(imageId, loaded, debounced) {
|
||||||
const imageData = this.imageDataMap.get(imageId);
|
const imageData = this.imageDataMap.get(imageId);
|
||||||
|
|
||||||
if (!imageData || imageData.loaded === loaded) {
|
if (!imageData || imageData.loaded === loaded) {
|
||||||
@@ -318,7 +376,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
imageData.loaded = loaded;
|
imageData.loaded = loaded;
|
||||||
this.framesStatus[imageData.index] = loaded;
|
this.framesStatus[imageData.index] = loaded;
|
||||||
this.loadedCount += loaded ? 1 : -1;
|
this.loadedCount += loaded ? 1 : -1;
|
||||||
this._updateProgress();
|
this._updateProgress(debounced);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setProgressData(progressId, progressData) {
|
_setProgressData(progressId, progressData) {
|
||||||
@@ -339,7 +397,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateProgress() {
|
_updateProgress(debounced) {
|
||||||
const totalFramesCount = this.stack.imageIds.length;
|
const totalFramesCount = this.stack.imageIds.length;
|
||||||
const loadedFramesCount = this.loadedCount;
|
const loadedFramesCount = this.loadedCount;
|
||||||
const loadingFramesCount = totalFramesCount - loadedFramesCount;
|
const loadingFramesCount = totalFramesCount - loadedFramesCount;
|
||||||
@@ -357,6 +415,11 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
framesStatus: this.framesStatus,
|
framesStatus: this.framesStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (debounced) {
|
||||||
|
this._debouncedSetProgressData(progressId, progressData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._setProgressData(progressId, progressData);
|
this._setProgressData(progressId, progressData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,11 +434,13 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
progressBar += ']';
|
progressBar += ']';
|
||||||
log.info(`${displaySetInstanceUID}: ${progressBar}`);
|
console.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StudyLoadingListener {
|
class StudyLoadingListener {
|
||||||
|
static events = StudyLoadingListenerEvents;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@@ -454,14 +519,36 @@ class StudyLoadingListener {
|
|||||||
|
|
||||||
_getSchema(stack) {
|
_getSchema(stack) {
|
||||||
const imageId = stack.imageIds[0];
|
const imageId = stack.imageIds[0];
|
||||||
|
if (!imageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const colonIndex = imageId.indexOf(':');
|
const colonIndex = imageId.indexOf(':');
|
||||||
return imageId.substring(0, colonIndex);
|
return imageId.substring(0, colonIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton
|
|
||||||
static getInstance(options) {
|
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) {
|
if (!StudyLoadingListener._instance) {
|
||||||
StudyLoadingListener._instance = new StudyLoadingListener(options);
|
StudyLoadingListener._instance = new StudyLoadingListener(
|
||||||
|
options || DEFAULT_OPTIONS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StudyLoadingListener._instance;
|
return StudyLoadingListener._instance;
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import log from '../log.js';
|
|
||||||
import OHIFError from './OHIFError';
|
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
|
||||||
import getImageId from '../utils/getImageId.js';
|
import getImageId from '../utils/getImageId.js';
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
export class StudyPrefetcher {
|
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.studies = studies || [];
|
||||||
this.prefetchDisplaySetsTimeout = 300;
|
|
||||||
this.lastActiveViewportElement = null;
|
if (options) {
|
||||||
|
this.options = { ...this.options, ...options };
|
||||||
|
this.options.requestType = 'prefetch';
|
||||||
|
}
|
||||||
|
|
||||||
cornerstone.events.addEventListener(
|
cornerstone.events.addEventListener(
|
||||||
'cornerstoneimagecachefull.StudyPrefetcher',
|
'cornerstoneimagecachefull.StudyPrefetcher',
|
||||||
@@ -16,6 +29,9 @@ export class StudyPrefetcher {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove previously set event listeners and stop prefetching.
|
||||||
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.stopPrefetching();
|
this.stopPrefetching();
|
||||||
cornerstone.events.removeEventListener(
|
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) {
|
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;
|
return StudyPrefetcher.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OHIF study metadata instances.
|
||||||
|
*
|
||||||
|
* @param {array} studies
|
||||||
|
*/
|
||||||
setStudies(studies) {
|
setStudies(studies) {
|
||||||
this.stopPrefetching();
|
this.stopPrefetching();
|
||||||
this.studies = studies;
|
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) {
|
if (!this.studies || !this.studies.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.element = element;
|
||||||
|
try {
|
||||||
|
this.enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to find the enabled element');
|
||||||
|
}
|
||||||
|
|
||||||
this.stopPrefetching();
|
this.stopPrefetching();
|
||||||
this.prefetchDisplaySets();
|
this.prefetchDisplaySets(displaySetInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop prefetching images.
|
||||||
|
*/
|
||||||
stopPrefetching() {
|
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);
|
clearTimeout(this.prefetchDisplaySetsHandler);
|
||||||
this.prefetchDisplaySetsHandler = setTimeout(() => {
|
this.prefetchDisplaySetsHandler = setTimeout(() => {
|
||||||
this.prefetchDisplaySets();
|
this.prefetchDisplaySets(element);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
prefetchDisplaySets() {
|
/**
|
||||||
// TODO: Allow passing in config
|
* Extract all image ids from all display sets to be fetched and
|
||||||
let config = {
|
* call method to add images to request pool manager.
|
||||||
order: 'closest',
|
*
|
||||||
displaySetCount: 1,
|
* @param {string} displaySetInstanceUID the display set instance uid
|
||||||
};
|
*/
|
||||||
|
prefetchDisplaySets(displaySetInstanceUID) {
|
||||||
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config);
|
const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(
|
||||||
|
displaySetInstanceUID
|
||||||
|
);
|
||||||
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
|
const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch);
|
||||||
|
|
||||||
this.prefetchImageIds(imageIds);
|
this.prefetchImageIds(imageIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add image ids to request pool manager.
|
||||||
|
*
|
||||||
|
* @param {array} imageIds
|
||||||
|
*/
|
||||||
prefetchImageIds(imageIds) {
|
prefetchImageIds(imageIds) {
|
||||||
const nonCachedImageIds = this.filterCachedImageIds(imageIds);
|
const nonCachedImageIds = this.filterCachedImageIds(imageIds);
|
||||||
const requestPoolManager = cornerstoneTools.requestPoolManager;
|
const imageLoadPoolManager = cornerstone.imageLoadPoolManager;
|
||||||
const requestType = 'prefetch';
|
|
||||||
const preventCache = false;
|
imageLoadPoolManager.maxNumRequests = {
|
||||||
const noop = () => {};
|
...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 => {
|
nonCachedImageIds.forEach(imageId => {
|
||||||
requestPoolManager.addRequest(
|
imageLoadPoolManager.addRequest(
|
||||||
{},
|
requestFn.bind(this, imageId),
|
||||||
imageId,
|
this.options.requestType,
|
||||||
requestType,
|
{
|
||||||
preventCache,
|
imageId,
|
||||||
noop,
|
}
|
||||||
noop
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
requestPoolManager.startGrabbing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get study by cornerstone image instance.
|
||||||
|
*
|
||||||
|
* @param {object} image
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
getStudy(image) {
|
getStudy(image) {
|
||||||
const StudyInstanceUID = cornerstone.metaData.get(
|
const StudyInstanceUID = cornerstone.metaData.get(
|
||||||
'StudyInstanceUID',
|
'StudyInstanceUID',
|
||||||
image.imageId
|
image.imageId
|
||||||
);
|
);
|
||||||
return OHIF.viewer.Studies.find(
|
const studies = this.studies;
|
||||||
study => study.StudyInstanceUID === StudyInstanceUID
|
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) {
|
getSeries(study, image) {
|
||||||
const SeriesInstanceUID = cornerstone.metaData.get(
|
const SeriesInstanceUID = cornerstone.metaData.get(
|
||||||
'SeriesInstanceUID',
|
'SeriesInstanceUID',
|
||||||
image.imageId
|
image.imageId
|
||||||
);
|
);
|
||||||
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
|
return study.getSeriesByUID(SeriesInstanceUID);
|
||||||
|
|
||||||
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sop instance by cornerstone image instance.
|
||||||
|
*
|
||||||
|
* @param {array} series
|
||||||
|
* @param {object} image
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
getInstance(series, image) {
|
getInstance(series, image) {
|
||||||
const instanceMetadata = cornerstone.metaData.get(
|
const instanceMetadata = cornerstone.metaData.get(
|
||||||
'instance',
|
'instance',
|
||||||
@@ -121,7 +222,33 @@ export class StudyPrefetcher {
|
|||||||
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
|
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 displaySets.find(displaySet => {
|
||||||
return displaySet.images.some(displaySetImage => {
|
return displaySet.images.some(displaySetImage => {
|
||||||
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
|
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();
|
const image = this.getActiveViewportImage();
|
||||||
|
|
||||||
if (!image || !config || !config.displaySetCount) {
|
if (!image) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*const study = this.getStudy(image);
|
const study = this.getStudy(image);
|
||||||
const series = this.getSeries(study, image);
|
const series = this.getSeries(study, image);
|
||||||
const instance = this.getInstance(series, image);*/
|
const instance = this.getInstance(series, image);
|
||||||
const displaySets = study.displaySets;
|
const displaySets = study.displaySets;
|
||||||
const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance);
|
const activeDisplaySet = displaySetInstanceUID
|
||||||
|
? this.getDisplaySetByUID(displaySetInstanceUID)
|
||||||
|
: this.getDisplaySetBySOPInstanceUID(displaySets, instance);
|
||||||
|
|
||||||
const prefetchMethodMap = {
|
const prefetchMethodMap = {
|
||||||
topdown: 'getFirstDisplaySets',
|
topdown: 'getFirstDisplaySets',
|
||||||
downward: 'getNextDisplaySets',
|
downward: 'getNextDisplaySets',
|
||||||
|
upward: 'getPreviousDisplaySets',
|
||||||
closest: 'getClosestDisplaySets',
|
closest: 'getClosestDisplaySets',
|
||||||
|
all: 'getAllDisplaySets',
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefetchOrder = config.order;
|
const prefetchOrder = this.options.order;
|
||||||
const methodName = prefetchMethodMap[prefetchOrder];
|
const methodName = prefetchMethodMap[prefetchOrder];
|
||||||
const getDisplaySets = this[methodName];
|
const getDisplaySets = this[methodName];
|
||||||
|
|
||||||
@@ -163,18 +313,59 @@ export class StudyPrefetcher {
|
|||||||
this,
|
this,
|
||||||
displaySets,
|
displaySets,
|
||||||
activeDisplaySet,
|
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 length = displaySets.length;
|
||||||
const selectedDisplaySets = [];
|
const selectedDisplaySets = [];
|
||||||
|
|
||||||
for (let i = 0; i < length && displaySetCount; i++) {
|
for (let i = 0; i < length && displaySetCount; i++) {
|
||||||
const displaySet = displaySets[i];
|
const displaySet = displaySets[i];
|
||||||
|
|
||||||
if (displaySet !== activeDisplaySet) {
|
if (includeActiveDisplaySet || displaySet !== activeDisplaySet) {
|
||||||
selectedDisplaySets.push(displaySet);
|
selectedDisplaySets.push(displaySet);
|
||||||
displaySetCount--;
|
displaySetCount--;
|
||||||
}
|
}
|
||||||
@@ -183,21 +374,78 @@ export class StudyPrefetcher {
|
|||||||
return selectedDisplaySets;
|
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 activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
|
||||||
const begin = activeDisplaySetIndex + 1;
|
const end = includeActiveDisplaySet
|
||||||
const end = Math.min(begin + displaySetCount, displaySets.length);
|
? 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);
|
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 activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet);
|
||||||
const length = displaySets.length;
|
const length = displaySets.length;
|
||||||
const selectedDisplaySets = [];
|
const selectedDisplaySets = [];
|
||||||
let left = activeDisplaySetIndex - 1;
|
let left = activeDisplaySetIndex - 1;
|
||||||
let right = activeDisplaySetIndex + 1;
|
let right = activeDisplaySetIndex + 1;
|
||||||
|
|
||||||
|
if (includeActiveDisplaySet) {
|
||||||
|
selectedDisplaySets.push(displaySets[activeDisplaySetIndex]);
|
||||||
|
displaySetCount--;
|
||||||
|
}
|
||||||
|
|
||||||
while ((left >= 0 || right < length) && displaySetCount) {
|
while ((left >= 0 || right < length) && displaySetCount) {
|
||||||
if (left >= 0) {
|
if (left >= 0) {
|
||||||
selectedDisplaySets.push(displaySets[left]);
|
selectedDisplaySets.push(displaySets[left]);
|
||||||
@@ -215,6 +463,12 @@ export class StudyPrefetcher {
|
|||||||
return selectedDisplaySets;
|
return selectedDisplaySets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all image ids from display sets.
|
||||||
|
*
|
||||||
|
* @param {array} displaySets
|
||||||
|
* @returns {array} image ids
|
||||||
|
*/
|
||||||
getImageIdsFromDisplaySets(displaySets) {
|
getImageIdsFromDisplaySets(displaySets) {
|
||||||
let imageIds = [];
|
let imageIds = [];
|
||||||
|
|
||||||
@@ -225,9 +479,19 @@ export class StudyPrefetcher {
|
|||||||
return imageIds;
|
return imageIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all image ids from a given display set.
|
||||||
|
*
|
||||||
|
* @param {array} displaySet
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
getImageIdsFromDisplaySet(displaySet) {
|
getImageIdsFromDisplaySet(displaySet) {
|
||||||
const imageIds = [];
|
const imageIds = [];
|
||||||
|
|
||||||
|
if (!displaySet.images || displaySet.images.length < 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This duplicates work done by the stack manager
|
// TODO: This duplicates work done by the stack manager
|
||||||
displaySet.images.forEach(image => {
|
displaySet.images.forEach(image => {
|
||||||
const numFrames = image.numFrames;
|
const numFrames = image.numFrames;
|
||||||
@@ -245,15 +509,30 @@ export class StudyPrefetcher {
|
|||||||
return imageIds;
|
return imageIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter cached image ids from a set of image ids.
|
||||||
|
*
|
||||||
|
* @param {array} imageIds
|
||||||
|
* @returns {array} images not cached
|
||||||
|
*/
|
||||||
filterCachedImageIds(imageIds) {
|
filterCachedImageIds(imageIds) {
|
||||||
return imageIds.filter(imageId => !this.isImageCached(imageId));
|
return imageIds.filter(imageId => !this.isImageCached(imageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if image id is cached in cornerstone.
|
||||||
|
*
|
||||||
|
* @param {string} imageId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
isImageCached(imageId) {
|
isImageCached(imageId) {
|
||||||
const image = cornerstone.imageCache.imageCache[imageId];
|
const image = cornerstone.imageCache.imageCache[imageId];
|
||||||
return image && image.sizeInBytes;
|
return image && image.sizeInBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warns that cache is full and stops prefetching.
|
||||||
|
*/
|
||||||
cacheFullHandler = () => {
|
cacheFullHandler = () => {
|
||||||
log.warn('Cache full');
|
log.warn('Cache full');
|
||||||
this.stopPrefetching();
|
this.stopPrefetching();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import CommandsManager from './CommandsManager.js';
|
|||||||
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
||||||
import HotkeysManager from './HotkeysManager.js';
|
import HotkeysManager from './HotkeysManager.js';
|
||||||
import ImageSet from './ImageSet';
|
import ImageSet from './ImageSet';
|
||||||
|
import LogManager from './LogManager';
|
||||||
|
import PubSub from './PubSub';
|
||||||
import MetadataProvider from './MetadataProvider';
|
import MetadataProvider from './MetadataProvider';
|
||||||
import OHIFError from './OHIFError.js';
|
import OHIFError from './OHIFError.js';
|
||||||
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
||||||
@@ -36,7 +38,9 @@ const classes = {
|
|||||||
MetadataProvider,
|
MetadataProvider,
|
||||||
CommandsManager,
|
CommandsManager,
|
||||||
HotkeysManager,
|
HotkeysManager,
|
||||||
|
LogManager,
|
||||||
ImageSet,
|
ImageSet,
|
||||||
|
PubSub,
|
||||||
StudyPrefetcher,
|
StudyPrefetcher,
|
||||||
StudyLoadingListener,
|
StudyLoadingListener,
|
||||||
StackLoadingListener,
|
StackLoadingListener,
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import { SeriesMetadata } from './SeriesMetadata';
|
|||||||
import { api } from 'dicomweb-client';
|
import { api } from 'dicomweb-client';
|
||||||
// - createStacks
|
// - createStacks
|
||||||
import { isImage } from '../../utils/isImage';
|
import { isImage } from '../../utils/isImage';
|
||||||
import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable';
|
import {
|
||||||
|
isDisplaySetReconstructable,
|
||||||
|
isSpacingUniform,
|
||||||
|
} from '../../utils/isDisplaySetReconstructable';
|
||||||
import errorHandler from '../../errorHandler';
|
import errorHandler from '../../errorHandler';
|
||||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||||
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
|
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
|
||||||
@@ -159,41 +162,64 @@ class StudyMetadata extends Metadata {
|
|||||||
// series into another display set.
|
// series into another display set.
|
||||||
const stackableInstances = [];
|
const stackableInstances = [];
|
||||||
series.forEachInstance(instance => {
|
series.forEachInstance(instance => {
|
||||||
|
let displaySet;
|
||||||
|
|
||||||
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
|
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
|
||||||
if (
|
if (
|
||||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||||
!instance.getTagValue('Rows')
|
!instance.getTagValue('Rows')
|
||||||
) {
|
) {
|
||||||
return;
|
// 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([]);
|
||||||
let displaySet;
|
const seriesData = series.getData();
|
||||||
|
|
||||||
if (isMultiFrame(instance)) {
|
|
||||||
displaySet = makeDisplaySet(series, [instance]);
|
|
||||||
|
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUIDs,
|
displaySetInstanceUID: displaySet.uid,
|
||||||
isClip: true,
|
SeriesDate: seriesData.SeriesDate,
|
||||||
|
SeriesTime: seriesData.SeriesTime,
|
||||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
SeriesNumber: instance.getTagValue('SeriesNumber'),
|
||||||
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
SeriesDescription: instance.getTagValue('SeriesDescription'),
|
||||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
numImageFrames: instance.getTagValue('NumberOfFrames'),
|
||||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
frameRate: instance.getTagValue('FrameTime'),
|
||||||
});
|
Modality: instance.getTagValue('Modality'),
|
||||||
displaySets.push(displaySet);
|
isMultiFrame: false,
|
||||||
} else if (isSingleImageModality(instance.Modality)) {
|
|
||||||
displaySet = makeDisplaySet(series, [instance]);
|
|
||||||
displaySet.setAttributes({
|
|
||||||
sopClassUIDs,
|
|
||||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
|
||||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||||
|
isReconstructable: false,
|
||||||
|
isModalitySupported: false,
|
||||||
|
metadata: instance.getData().metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
} else {
|
} 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));
|
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the source display set of the derivated display set.
|
* Returns the source display set of the derivated display set.
|
||||||
* @param {object} derivatedDisplaySet
|
* @param {object} derivatedDisplaySet
|
||||||
@@ -242,40 +267,45 @@ class StudyMetadata extends Metadata {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const otherDisplaySets = allDisplaySets.filter(
|
const otherDisplaySets = allDisplaySets.filter(
|
||||||
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
|
ds =>
|
||||||
|
ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const { metadata } = derivatedDisplaySet;
|
const { metadata } = derivatedDisplaySet;
|
||||||
|
|
||||||
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
|
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
|
||||||
(metadata, otherDisplaySets);
|
metadata,
|
||||||
|
otherDisplaySets
|
||||||
|
);
|
||||||
|
|
||||||
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
|
let noReferencedSeriesAvailable =
|
||||||
|
!referencedSeriesInstanceUIDs ||
|
||||||
referencedSeriesInstanceUIDs.length === 0;
|
referencedSeriesInstanceUIDs.length === 0;
|
||||||
if (noReferencedSeriesAvailable) {
|
if (noReferencedSeriesAvailable) {
|
||||||
referencedSeriesInstanceUIDs =
|
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
|
||||||
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
|
metadata
|
||||||
(metadata);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
|
noReferencedSeriesAvailable =
|
||||||
|
!referencedSeriesInstanceUIDs ||
|
||||||
referencedSeriesInstanceUIDs.length === 0;
|
referencedSeriesInstanceUIDs.length === 0;
|
||||||
if (noReferencedSeriesAvailable) {
|
if (noReferencedSeriesAvailable) {
|
||||||
referencedSeriesInstanceUIDs =
|
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
|
||||||
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
|
metadata,
|
||||||
(metadata, otherDisplaySets);
|
otherDisplaySets
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const referencedSeriesAvailable = referencedSeriesInstanceUIDs &&
|
const referencedSeriesAvailable =
|
||||||
referencedSeriesInstanceUIDs.length !== 0;
|
referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0;
|
||||||
if (referencedSeriesAvailable) {
|
if (referencedSeriesAvailable) {
|
||||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||||
);
|
);
|
||||||
;
|
|
||||||
return referencedDisplaySet;
|
return referencedDisplaySet;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of derived datasets in the study, filtered by the given filter.
|
* Returns a list of derived datasets in the study, filtered by the given filter.
|
||||||
@@ -303,7 +333,10 @@ class StudyMetadata extends Metadata {
|
|||||||
if (referencedSeriesInstanceUID) {
|
if (referencedSeriesInstanceUID) {
|
||||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||||
displaySet => {
|
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) {
|
if (referencedFrameOfReferenceUID) {
|
||||||
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
|
||||||
displaySet =>
|
displaySet =>
|
||||||
displaySet.FrameOfReferenceUID ===
|
displaySet.FrameOfReferenceUID === referencedFrameOfReferenceUID
|
||||||
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.splice(insertIndex, 0, displaySet);
|
||||||
this.displaySets = this._displaySets;
|
this.displaySets = this._displaySets;
|
||||||
}
|
}
|
||||||
@@ -861,8 +901,9 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
imageSet.sortByImagePositionPatient();
|
imageSet.sortByImagePositionPatient();
|
||||||
|
|
||||||
// check if the spacing is uniform and update isReconstructable
|
// check if the spacing is uniform and update isReconstructable
|
||||||
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find
|
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find(
|
||||||
(issue => issue === ReconstructionIssues.DATASET_4D);
|
issue => issue === ReconstructionIssues.DATASET_4D
|
||||||
|
);
|
||||||
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
|
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
|
||||||
imageSet.isReconstructable = displaySpacingInfo.isUniform;
|
imageSet.isReconstructable = displaySpacingInfo.isUniform;
|
||||||
|
|
||||||
@@ -875,11 +916,15 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
|
|
||||||
if (!imageSet.displayReconstructableInfo) {
|
if (!imageSet.displayReconstructableInfo) {
|
||||||
// It is not reconstrabale Save type of warning
|
// It is not reconstrabale Save type of warning
|
||||||
imageSet.reconstructionIssues = displaySpacingInfo ?
|
imageSet.reconstructionIssues = displaySpacingInfo
|
||||||
displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) :
|
? displayReconstructableInfo.reconstructionIssues.concat(
|
||||||
displayReconstructableInfo.reconstructionIssues;
|
displaySpacingInfo.reconstructionIssues
|
||||||
|
)
|
||||||
|
: displayReconstructableInfo.reconstructionIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageSet.isModalitySupported = true;
|
||||||
|
|
||||||
return imageSet;
|
return imageSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -957,38 +1002,36 @@ function _getDisplaySetFromSopClassModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the referenced series instance UIDs by searching the information in the
|
* Returns the referenced series instance UIDs by searching the information in the
|
||||||
* ReferencedSeriesSequence.
|
* ReferencedSeriesSequence.
|
||||||
* @param {object} derivatedDisplaySet.metadata
|
* @param {object} derivatedDisplaySet.metadata
|
||||||
* @return {array[string]} referenced series instance UIDs.
|
* @return {array[string]} referenced series instance UIDs.
|
||||||
*/
|
*/
|
||||||
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence (
|
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
|
||||||
metadata,
|
metadata
|
||||||
) {
|
) {
|
||||||
if (!metadata.ReferencedSeriesSequence) {
|
if (!metadata.ReferencedSeriesSequence) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let referencedSeriesInstanceUIDs;
|
let referencedSeriesInstanceUIDs;
|
||||||
const ReferencedSeriesSequence = _toArray(
|
const ReferencedSeriesSequence = _toArray(metadata.ReferencedSeriesSequence);
|
||||||
metadata.ReferencedSeriesSequence
|
|
||||||
);
|
|
||||||
|
|
||||||
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return referencedSeriesInstanceUIDs;
|
return referencedSeriesInstanceUIDs;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the referenced series instance UIDs by searching the information in the
|
* Returns the referenced series instance UIDs by searching the information in the
|
||||||
* ReferencedImageSequence.
|
* ReferencedImageSequence.
|
||||||
* @param {object} derivatedDisplaySet.metadata
|
* @param {object} derivatedDisplaySet.metadata
|
||||||
* @param {array[object]} displaysets
|
* @param {array[object]} displaysets
|
||||||
* @return {array[string]} referenced series instance UIDs.
|
* @return {array[string]} referenced series instance UIDs.
|
||||||
*/
|
*/
|
||||||
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
|
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
|
||||||
metadata,
|
metadata,
|
||||||
displaySets
|
displaySets
|
||||||
) {
|
) {
|
||||||
@@ -1009,22 +1052,25 @@ function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
|
|||||||
ReferencedSOPInstanceUID
|
ReferencedSOPInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
|
if (
|
||||||
|
referencedSeriesInstanceUIDs &&
|
||||||
|
referencedSeriesInstanceUIDs.length !== 0
|
||||||
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return referencedSeriesInstanceUIDs;
|
return referencedSeriesInstanceUIDs;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the referenced series instance UIDs by searching the information in the
|
* Returns the referenced series instance UIDs by searching the information in the
|
||||||
* SourceImageSequence.
|
* SourceImageSequence.
|
||||||
* @param {object} derivatedDisplaySet.metadata
|
* @param {object} derivatedDisplaySet.metadata
|
||||||
* @param {array[object]} displaysets
|
* @param {array[object]} displaysets
|
||||||
* @return {array[string]} referenced series instance UIDs.
|
* @return {array[string]} referenced series instance UIDs.
|
||||||
*/
|
*/
|
||||||
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
|
||||||
metadata,
|
metadata,
|
||||||
displaySets
|
displaySets
|
||||||
) {
|
) {
|
||||||
@@ -1034,9 +1080,7 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
|||||||
SourceImageSequence = metadata.SourceImageSequence;
|
SourceImageSequence = metadata.SourceImageSequence;
|
||||||
} else {
|
} else {
|
||||||
const { PerFrameFunctionalGroupsSequence } = metadata;
|
const { PerFrameFunctionalGroupsSequence } = metadata;
|
||||||
const firstFunctionalGroups = _toArray(
|
const firstFunctionalGroups = _toArray(PerFrameFunctionalGroupsSequence)[0];
|
||||||
PerFrameFunctionalGroupsSequence
|
|
||||||
)[0];
|
|
||||||
if (firstFunctionalGroups) {
|
if (firstFunctionalGroups) {
|
||||||
const { DerivationImageSequence } = firstFunctionalGroups;
|
const { DerivationImageSequence } = firstFunctionalGroups;
|
||||||
SourceImageSequence = DerivationImageSequence;
|
SourceImageSequence = DerivationImageSequence;
|
||||||
@@ -1056,22 +1100,25 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
|
|||||||
displaySets,
|
displaySets,
|
||||||
ReferencedSOPInstanceUID
|
ReferencedSOPInstanceUID
|
||||||
);
|
);
|
||||||
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
|
if (
|
||||||
|
referencedSeriesInstanceUIDs &&
|
||||||
|
referencedSeriesInstanceUIDs.length !== 0
|
||||||
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return referencedSeriesInstanceUIDs;
|
return referencedSeriesInstanceUIDs;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the referenced series instance UIDs by searching the information in the
|
* Returns the referenced series instance UIDs by searching the information in the
|
||||||
* SOPInstanceUID of the displaySets.
|
* SOPInstanceUID of the displaySets.
|
||||||
* @param {array[object]} displaysets
|
* @param {array[object]} displaysets
|
||||||
* @param {string} SOPInstanceUID
|
* @param {string} SOPInstanceUID
|
||||||
* @return {array[string]} referenced series instance UIDs.
|
* @return {array[string]} referenced series instance UIDs.
|
||||||
*/
|
*/
|
||||||
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
|
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
|
||||||
displaySets,
|
displaySets,
|
||||||
SOPInstanceUID
|
SOPInstanceUID
|
||||||
) {
|
) {
|
||||||
@@ -1092,10 +1139,10 @@ function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function _toArray(arrayOrObject) {
|
function _toArray(arrayOrObject) {
|
||||||
return Array.isArray(arrayOrObject) ? arrayOrObject : [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 user from './user.js';
|
||||||
import errorHandler from './errorHandler.js';
|
import errorHandler from './errorHandler.js';
|
||||||
import utils, { hotkeys } from './utils/';
|
import utils, { hotkeys } from './utils/';
|
||||||
|
import str2ab from './utils/str2ab';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UINotificationService,
|
UINotificationService,
|
||||||
@@ -96,6 +97,8 @@ export {
|
|||||||
UIDialogService,
|
UIDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
LoggerService,
|
LoggerService,
|
||||||
|
|
||||||
|
str2ab,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OHIF };
|
export { OHIF };
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ describe('Top level exports', () => {
|
|||||||
'cornerstone',
|
'cornerstone',
|
||||||
'default', //
|
'default', //
|
||||||
'errorHandler',
|
'errorHandler',
|
||||||
|
'str2ab',
|
||||||
'string',
|
'string',
|
||||||
'ui',
|
'ui',
|
||||||
'user',
|
'user',
|
||||||
|
|||||||
@@ -802,6 +802,10 @@ export default class MeasurementApi {
|
|||||||
collection.push(addedMeasurement);
|
collection.push(addedMeasurement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (measurement.isReadOnly) {
|
||||||
|
addedMeasurement.isReadOnly = measurement.isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
if (!emptyItem) {
|
if (!emptyItem) {
|
||||||
// Reflect the entry in the tool group collection
|
// Reflect the entry in the tool group collection
|
||||||
groupCollection.push({
|
groupCollection.push({
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
export default function(element) {
|
export default function(element, imageId) {
|
||||||
// Get the Cornerstone imageId
|
if (!imageId) {
|
||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
// Get the Cornerstone imageId
|
||||||
const imageId = enabledElement.image.imageId;
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
imageId = enabledElement.image.imageId;
|
||||||
|
}
|
||||||
|
|
||||||
// Get StudyInstanceUID & PatientID
|
// Get StudyInstanceUID & PatientID
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const dicomSRDisplayTool = {
|
||||||
|
id: 'DICOMSRDisplayTool',
|
||||||
|
name: 'DICOMSRDisplayTool',
|
||||||
|
toolGroup: 'allTools',
|
||||||
|
cornerstoneToolType: 'DICOMSRDisplayTool',
|
||||||
|
options: {
|
||||||
|
measurementTable: {
|
||||||
|
displayFunction: data => {
|
||||||
|
return `(SR) ${data.lesionNamingNumber ||
|
||||||
|
data.measurementNumber ||
|
||||||
|
data.text ||
|
||||||
|
''}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
caseProgress: {
|
||||||
|
include: true,
|
||||||
|
evaluate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dicomSRDisplayTool;
|
||||||
@@ -10,6 +10,7 @@ import { angle } from './angle';
|
|||||||
import { targetCR } from './targetCR';
|
import { targetCR } from './targetCR';
|
||||||
import { targetNE } from './targetNE';
|
import { targetNE } from './targetNE';
|
||||||
import { targetUN } from './targetUN';
|
import { targetUN } from './targetUN';
|
||||||
|
import dicomSRDisplayTool from './dicomSRDisplayTool';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
arrowAnnotate,
|
arrowAnnotate,
|
||||||
@@ -24,4 +25,5 @@ export {
|
|||||||
targetCR,
|
targetCR,
|
||||||
targetNE,
|
targetNE,
|
||||||
targetUN,
|
targetUN,
|
||||||
|
dicomSRDisplayTool,
|
||||||
};
|
};
|
||||||
@@ -78,17 +78,6 @@ export const setActiveViewportSpecificData = viewportSpecificData => ({
|
|||||||
/**
|
/**
|
||||||
* NOT-VIEWPORT
|
* 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 => ({
|
export const setUserPreferences = state => ({
|
||||||
type: SET_USER_PREFERENCES,
|
type: SET_USER_PREFERENCES,
|
||||||
state,
|
state,
|
||||||
@@ -134,8 +123,6 @@ const actions = {
|
|||||||
/**
|
/**
|
||||||
* NOT-VIEWPORT
|
* NOT-VIEWPORT
|
||||||
*/
|
*/
|
||||||
setStudyLoadingProgress,
|
|
||||||
clearStudyLoadingProgress,
|
|
||||||
setUserPreferences,
|
setUserPreferences,
|
||||||
setExtensionData,
|
setExtensionData,
|
||||||
setTimepoints,
|
setTimepoints,
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ describe('actions', () => {
|
|||||||
'setLayout',
|
'setLayout',
|
||||||
'clearViewportSpecificData',
|
'clearViewportSpecificData',
|
||||||
'setActiveViewportSpecificData',
|
'setActiveViewportSpecificData',
|
||||||
'setStudyLoadingProgress',
|
|
||||||
'clearStudyLoadingProgress',
|
|
||||||
'setUserPreferences',
|
'setUserPreferences',
|
||||||
'setExtensionData',
|
'setExtensionData',
|
||||||
'setTimepoints',
|
'setTimepoints',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import extensions from './extensions';
|
import extensions from './extensions';
|
||||||
import loading from './loading';
|
|
||||||
import preferences from './preferences';
|
import preferences from './preferences';
|
||||||
import servers from './servers';
|
import servers from './servers';
|
||||||
import studies from './studies';
|
import studies from './studies';
|
||||||
@@ -8,7 +7,6 @@ import viewports from './viewports';
|
|||||||
|
|
||||||
const reducers = {
|
const reducers = {
|
||||||
extensions,
|
extensions,
|
||||||
loading,
|
|
||||||
preferences,
|
preferences,
|
||||||
servers,
|
servers,
|
||||||
studies,
|
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;
|
|
||||||
Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user