Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94a9067fe3 | ||
|
|
cfba4df70e | ||
|
|
11c1d12872 | ||
|
|
91364db882 | ||
|
|
bea31d5fec | ||
|
|
48ecbc261a | ||
|
|
8bc467fb07 | ||
|
|
cffc44f0f4 | ||
|
|
ea0fc07e30 | ||
|
|
b8d241fa33 | ||
|
|
ccdfcd4a85 | ||
|
|
6cf72d1757 | ||
|
|
bb1cdbda3a | ||
|
|
fab122dd8f | ||
|
|
42803b0ae8 | ||
|
|
5a78da8ff6 | ||
|
|
0ef88e88eb | ||
|
|
a7b5f1847a | ||
|
|
5cb6e63b88 | ||
|
|
48eb4a9ec3 | ||
|
|
d9231fd431 | ||
|
|
e5b578bd8e | ||
|
|
f38979037d | ||
|
|
45ca88f5b8 | ||
|
|
a1dee16a3d | ||
|
|
6a37a2a661 | ||
|
|
a8caf4bba4 | ||
|
|
29dcc82e28 | ||
|
|
dbf0501031 | ||
|
|
ef28fa2ac1 | ||
|
|
779a7e0976 | ||
|
|
7f3ca1fc23 | ||
|
|
59ab68b4ed | ||
|
|
248bc0a64a | ||
|
|
5fdace1432 | ||
|
|
71b6b454a3 | ||
|
|
3b02a06f5e | ||
|
|
ebdcde1c4d | ||
|
|
829ac2b410 | ||
|
|
09745a0349 | ||
|
|
cd73dd3027 | ||
|
|
ca511edeff | ||
|
|
bb5f30ce2a | ||
|
|
87d5c68e71 | ||
|
|
0e87ab37c2 | ||
|
|
756b005fe1 | ||
|
|
3d86b5f876 | ||
|
|
2a647fd618 | ||
|
|
f8fc31bcac | ||
|
|
b7ab88b0f1 | ||
|
|
57a30d7c34 | ||
|
|
cd90665b0e | ||
|
|
98c657231e | ||
|
|
14da247318 | ||
|
|
6a77d6c778 | ||
|
|
8f1785446b | ||
|
|
41225eefc0 | ||
|
|
7772fee21a | ||
|
|
d870ac914a | ||
|
|
451f7eab92 | ||
|
|
029f8af4c7 | ||
|
|
b1b4c8c861 | ||
|
|
cdde2af224 | ||
|
|
4a2922dda0 | ||
|
|
b6b5a8414f | ||
|
|
e7cc735c03 | ||
|
|
6121bdcf63 | ||
|
|
da70079675 | ||
|
|
9089875ca7 | ||
|
|
b4627ecfa3 | ||
|
|
4ede8a525a | ||
|
|
2c23818c86 | ||
|
|
fa4b03d62d | ||
|
|
698e900b85 | ||
|
|
b7d47383c5 | ||
|
|
6123741765 | ||
|
|
ab25be3520 | ||
|
|
950a54c704 | ||
|
|
4293c073f0 | ||
|
|
62a2cd5ff3 | ||
|
|
2d827c2a49 | ||
|
|
a8305772d0 | ||
|
|
eab115e70c | ||
|
|
c02b232b0c | ||
|
|
f01640d009 | ||
|
|
63fd65690c | ||
|
|
944afe4a3e | ||
|
|
b1678ce639 | ||
|
|
39937cca08 | ||
|
|
f7b8b6a41c | ||
|
|
79fe442293 | ||
|
|
875238e5b0 | ||
|
|
d2d3ca2828 | ||
|
|
cebea832a3 | ||
|
|
c4f84b1174 | ||
|
|
f9fe4232e7 | ||
|
|
42c22df1b6 | ||
|
|
19a8e71317 | ||
|
|
fa95bae6ef | ||
|
|
8a20a0526e | ||
|
|
5b20d8f323 | ||
|
|
9ea37a2201 | ||
|
|
2a66e175df | ||
|
|
77a421cdc0 | ||
|
|
7033886697 | ||
|
|
256563d7ee | ||
|
|
f7162ce617 | ||
|
|
027ef4f93c | ||
|
|
f09ebbe2e8 | ||
|
|
51b4e60161 | ||
|
|
08de987711 | ||
|
|
20e366ffbd | ||
|
|
561911056a | ||
|
|
835f64d47a | ||
|
|
fc40d84092 | ||
|
|
709f14708e |
No files matched your search
@@ -28,6 +28,12 @@ if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
|||||||
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "${PORT}" ]
|
||||||
|
then
|
||||||
|
echo "Changing port to ${PORT}..."
|
||||||
|
sed -i -e "s/listen 80/listen ${PORT}/g" /etc/nginx/conf.d/default.conf
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Starting Nginx to serve the OHIF Viewer..."
|
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
@@ -11,6 +11,7 @@ node -v
|
|||||||
|
|
||||||
# Install build deps and all monorepo package dependencies. Yarn Workspaces
|
# Install build deps and all monorepo package dependencies. Yarn Workspaces
|
||||||
# should also symlink all projects appropriately
|
# should also symlink all projects appropriately
|
||||||
|
yarn run lerna:restore
|
||||||
yarn install --no-ignore-optional --pure-lockfile
|
yarn install --no-ignore-optional --pure-lockfile
|
||||||
|
|
||||||
# Build && Move PWA Output
|
# Build && Move PWA Output
|
||||||
@@ -21,6 +22,19 @@ mv platform/viewer/dist/* .netlify/www/pwa -v
|
|||||||
# Build && Move script output
|
# Build && Move script output
|
||||||
# yarn run build:package
|
# yarn run build:package
|
||||||
|
|
||||||
|
# Build && Move Docz Output
|
||||||
|
# Using local yarn install to prevent Gatsby from needing to access
|
||||||
|
# node_modules above the platform/ui folder
|
||||||
|
cd platform/ui
|
||||||
|
yarn install
|
||||||
|
yarn run build
|
||||||
|
cd ../..
|
||||||
|
mkdir -p ./.netlify/www/ui
|
||||||
|
mv platform/ui/.docz/dist/* .netlify/www/ui -v
|
||||||
|
|
||||||
|
# Cache all of the node_module dependencies in
|
||||||
|
# extensions, modules, and platform packages
|
||||||
|
yarn run lerna:cache
|
||||||
echo 'Nothing left to see here. Go home, folks.'
|
echo 'Nothing left to see here. Go home, folks.'
|
||||||
|
|
||||||
# Build using react-scripts
|
# Build using react-scripts
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ window.config = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Install the viewer:
|
- Install the viewer:
|
||||||
`window.OHIFStandaloneViewer.installViewer(window.config);`
|
`window.OHIFViewer.installViewer(window.config);`
|
||||||
|
|
||||||
This exact setup is demonstrated in this
|
This exact setup is demonstrated in this
|
||||||
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
|
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
|
||||||
|
|||||||
@@ -127,10 +127,107 @@ appropriate headers. You can find an example of this setup in our
|
|||||||
|
|
||||||
#### What if my archive doesn't support DicomWeb?
|
#### What if my archive doesn't support DicomWeb?
|
||||||
|
|
||||||
> This is possible to do with the OHIF Viewer, but not as straightforward. Look
|
It's possible to supply all Study data via JSON format, in the event you do not have a DicomWeb endpoint.
|
||||||
> out for documentation on this subject in the near future.
|
You can host all of the relevant files on any web accessible server (Amazon S3, Azure Blob Storage, Local file server etc.)
|
||||||
|
|
||||||
|
This JSON is supplied via the '?url=' query parameter.
|
||||||
|
It should reference an endpoint that returns **application/json** formatted text.
|
||||||
|
|
||||||
|
If you do not have an API, you can simply return a text file containing the JSON from any web server.
|
||||||
|
|
||||||
|
|
||||||
|
You tell the OHIF viewer to use JSON by appending the `'?url='` query to the `/Viewer` route:
|
||||||
|
|
||||||
|
eg. `https://my-test-ohif-server/viewer?url=https://my-json-server/study-uid.json`
|
||||||
|
|
||||||
|
The returned JSON object must contain a single root object with a 'studies' array.
|
||||||
|
|
||||||
|
|
||||||
|
*Sample JSON format:*
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"studies": [
|
||||||
|
{
|
||||||
|
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
|
||||||
|
"StudyDescription": "BRAIN SELLA",
|
||||||
|
"StudyDate": "20010108",
|
||||||
|
"StudyTime": "120022",
|
||||||
|
"PatientName": "MISTER^MR",
|
||||||
|
"PatientId": "832040",
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"SeriesDescription": "SAG T-1",
|
||||||
|
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
|
||||||
|
"SeriesNumber": 2,
|
||||||
|
"SeriesDate": "20010108",
|
||||||
|
"SeriesTime": "120318",
|
||||||
|
"Modality": "MR",
|
||||||
|
"instances": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"Columns": 512,
|
||||||
|
"Rows": 512,
|
||||||
|
"InstanceNumber": 3,
|
||||||
|
"AcquisitionNumber": 0,
|
||||||
|
"PhotometricInterpretation": "MONOCHROME2",
|
||||||
|
"BitsAllocated": 16,
|
||||||
|
"BitsStored": 16,
|
||||||
|
"PixelRepresentation": 1,
|
||||||
|
"SamplesPerPixel": 1,
|
||||||
|
"PixelSpacing": [0.390625, 0.390625],
|
||||||
|
"HighBit": 15,
|
||||||
|
"ImageOrientationPatient": [0,1,0,0,0,-1],
|
||||||
|
"ImagePositionPatient": [11.600000,-92.500000, 98.099998],
|
||||||
|
"FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470",
|
||||||
|
"ImageType": ["ORIGINAL","PRIMARY","OTHER"],
|
||||||
|
"Modality": "MR",
|
||||||
|
"SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124",
|
||||||
|
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
|
||||||
|
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78"
|
||||||
|
},
|
||||||
|
"url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
More info on this JSON format can be found here [Issue #1500](https://github.com/OHIF/Viewers/issues/1500)
|
||||||
|
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
|
||||||
|
1. When hosting the viewer, you will also need to host a /viewer route on the server - or the browser may not be able to find the route.
|
||||||
|
2. For each instance url (dicom object) in the returned JSON, you must prefix the `url` with `dicomweb:` in order for the cornerstone image loader to retrieve it correctly.
|
||||||
|
eg. `https://image-server/my-image.dcm` ---> `dicomweb:https://image-server/my-image.dcm`
|
||||||
|
3. The JSON format above is compatible with >= v3.7.8 of the application. Older versions of the viewer used a different JSON format. As of 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does not support this format yet.
|
||||||
|
4. The JSON format is case-sensitive. Please ensure you have matched casing with the naturalised Dicom format referenced in [Issue #1500](https://github.com/OHIF/Viewers/issues/1500).
|
||||||
|
|
||||||
|
*CORS Issues (Cross-Origin Resource Sharing)*
|
||||||
|
|
||||||
|
If you host a JSON API or Images on a different domain from the the app itself, you will likely have CORS issues. This will also happen when testing from Localhost and reaching out to remote servers.
|
||||||
|
Even if the domain is the same, different ports, subdomains or protocols (https vs http) will also cause CORS errors.
|
||||||
|
You will to need add a configuration on each server hosting these assets to allow your App server origin.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
Lets assume your application is hosted on `https://my-ohif-server.com`.
|
||||||
|
|
||||||
|
Your JSON API is hosted on `https://my-json-api.aws.com`
|
||||||
|
|
||||||
|
And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com`
|
||||||
|
|
||||||
|
When you first start your application, browsing to `https://my-ohif-server.com/viewer?url=https://my-json-api.aws.com/api/my-json-study-info.json`, you will likely get a CORS error in the browser console as it tries to connect to `https://my-json-api.aws.com`.
|
||||||
|
|
||||||
|
Adding a setting on the JSON server to allow the CORS origin = `https://my-ohif-server.com` should solve this.
|
||||||
|
|
||||||
|
Next, you will likely get a similar CORS error, as the browser tries to go to `https://my-s3-bucket.aws.com`.
|
||||||
|
You will need to go to the S3 bucket configuration, and add a CORS setting to allow origin = `https://my-ohif-server.com`.
|
||||||
|
|
||||||
|
Essentially, whenever the application connects to a remote resource, you will need to add the applications url to the allowed CORS Origins on that resource. Adding an origin similar to https://localhost:3000 will also allow for local testing.
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
### Securing Your Data
|
### Securing Your Data
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,164 @@
|
|||||||
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.9.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.4...@ohif/extension-cornerstone@2.9.5) (2020-09-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.3...@ohif/extension-cornerstone@2.9.4) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.2...@ohif/extension-cornerstone@2.9.3) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.1...@ohif/extension-cornerstone@2.9.2) (2020-08-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Updated react-cornerstone-viewport to version 4.0.2 ([#2001](https://github.com/OHIF/Viewers/issues/2001)) ([59ab68b](https://github.com/OHIF/Viewers/commit/59ab68b4edbfec010170c4429c300a53f688bf94))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.0...@ohif/extension-cornerstone@2.9.1) (2020-08-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **stackPrefetch:** Added stackPrefetch config with 20 max concurrent requests ([#2000](https://github.com/OHIF/Viewers/issues/2000)) ([3b02a06](https://github.com/OHIF/Viewers/commit/3b02a06f5e250660edde4862ce44147db3dc3ab9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.9.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.5...@ohif/extension-cornerstone@2.9.0) (2020-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.4...@ohif/extension-cornerstone@2.8.5) (2020-08-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.3...@ohif/extension-cornerstone@2.8.4) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.2...@ohif/extension-cornerstone@2.8.3) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.1...@ohif/extension-cornerstone@2.8.2) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.8.0...@ohif/extension-cornerstone@2.8.1) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Infinite frame index change when quickly jumping between ([#1734](https://github.com/OHIF/Viewers/issues/1734)) ([a830577](https://github.com/OHIF/Viewers/commit/a8305772d0fbec506549ad7ea08504a8bc3a4f39)), closes [#1733](https://github.com/OHIF/Viewers/issues/1733)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.3...@ohif/extension-cornerstone@2.8.0) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.1...@ohif/extension-cornerstone@2.7.2) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.0...@ohif/extension-cornerstone@2.7.1) (2020-05-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.0...@ohif/extension-cornerstone@2.6.1) (2020-04-23)
|
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.0...@ohif/extension-cornerstone@2.6.1) (2020-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.6.1",
|
"version": "2.9.5",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "4.12.5",
|
"cornerstone-tools": "^4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^3.1.0",
|
||||||
"dcmjs": "^0.10.1",
|
"dcmjs": "0.16.3",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
@@ -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": "^2.3.6"
|
"react-cornerstone-viewport": "4.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ class OHIFCornerstoneViewport 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
|
||||||
) {
|
) {
|
||||||
@@ -215,12 +215,28 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newImageHandler = ({ currentImageIdIndex, sopInstanceUid }) => {
|
||||||
|
const { displaySet } = this.props.viewportData;
|
||||||
|
const { StudyInstanceUID } = displaySet;
|
||||||
|
|
||||||
|
if (currentImageIdIndex > 0) {
|
||||||
|
this.props.onNewImage({
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPInstanceUID: sopInstanceUid,
|
||||||
|
frameIndex: currentImageIdIndex,
|
||||||
|
activeViewportIndex: viewportIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConnectedCornerstoneViewport
|
<ConnectedCornerstoneViewport
|
||||||
viewportIndex={viewportIndex}
|
viewportIndex={viewportIndex}
|
||||||
imageIds={imageIds}
|
imageIds={imageIds}
|
||||||
imageIdIndex={currentImageIdIndex}
|
imageIdIndex={currentImageIdIndex}
|
||||||
|
onNewImage={newImageHandler}
|
||||||
|
onNewImageDebounceTime={700}
|
||||||
// ~~ Connected (From REDUX)
|
// ~~ Connected (From REDUX)
|
||||||
// frameRate={frameRate}
|
// frameRate={frameRate}
|
||||||
// isPlaying={false}
|
// isPlaying={false}
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ const scroll = cornerstoneTools.import('util/scroll');
|
|||||||
const { studyMetadataManager } = OHIF.utils;
|
const { studyMetadataManager } = OHIF.utils;
|
||||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||||
|
|
||||||
|
const refreshCornerstoneViewports = () => {
|
||||||
|
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||||
|
if (enabledElement.image) {
|
||||||
|
cornerstone.updateImage(enabledElement.element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const commandsModule = ({ servicesManager }) => {
|
const commandsModule = ({ servicesManager }) => {
|
||||||
const actions = {
|
const actions = {
|
||||||
rotateViewport: ({ viewports, rotation }) => {
|
rotateViewport: ({ viewports, rotation }) => {
|
||||||
@@ -180,10 +188,7 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
|
|
||||||
measurementApi.syncMeasurementsAndToolData();
|
measurementApi.syncMeasurementsAndToolData();
|
||||||
|
|
||||||
// Update images in all active viewports
|
refreshCornerstoneViewports();
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
|
||||||
cornerstone.updateImage(enabledElement.element);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
|
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
|
||||||
const nearbyTool = {};
|
const nearbyTool = {};
|
||||||
@@ -262,23 +267,26 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
activeViewportIndex
|
activeViewportIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const study = studyMetadataManager.get(StudyInstanceUID);
|
const study = studyMetadataManager.get(StudyInstanceUID);
|
||||||
|
|
||||||
const displaySet = study.findDisplaySet(ds => {
|
const displaySet = study.findDisplaySet(ds => {
|
||||||
return ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
|
return (
|
||||||
|
ds.images &&
|
||||||
|
ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
displaySet.SOPInstanceUID = SOPInstanceUID;
|
displaySet.SOPInstanceUID = SOPInstanceUID;
|
||||||
displaySet.frameIndex = frameIndex;
|
displaySet.frameIndex = frameIndex;
|
||||||
|
|
||||||
window.store.dispatch(setViewportSpecificData(activeViewportIndex, displaySet));
|
window.store.dispatch(
|
||||||
|
setViewportSpecificData(activeViewportIndex, displaySet)
|
||||||
|
);
|
||||||
|
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
refreshCornerstoneViewports();
|
||||||
cornerstone.updateImage(enabledElement.element);
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import init from './init.js';
|
|||||||
import commandsModule from './commandsModule.js';
|
import commandsModule from './commandsModule.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import('./OHIFCornerstoneViewport');
|
return import('./OHIFCornerstoneViewport');
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'cornerstone',
|
id: 'cornerstone',
|
||||||
|
version,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -34,8 +36,17 @@ export default {
|
|||||||
preRegistration({ servicesManager, configuration = {} }) {
|
preRegistration({ servicesManager, configuration = {} }) {
|
||||||
init({ servicesManager, configuration });
|
init({ servicesManager, configuration });
|
||||||
},
|
},
|
||||||
getViewportModule() {
|
getViewportModule({ commandsManager }) {
|
||||||
return OHIFCornerstoneViewport;
|
const ExtendedOHIFCornerstoneViewport = props => {
|
||||||
|
const onNewImageHandler = jumpData => {
|
||||||
|
commandsManager.runCommand('jumpToImage', jumpData);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ExtendedOHIFCornerstoneViewport;
|
||||||
},
|
},
|
||||||
getToolbarModule() {
|
getToolbarModule() {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
|
|||||||
@@ -34,4 +34,11 @@ export default function(configuration = {}) {
|
|||||||
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
|
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
|
||||||
|
|
||||||
cornerstoneTools.store.state.touchProximity = 40;
|
cornerstoneTools.store.state.touchProximity = 40;
|
||||||
|
|
||||||
|
// Configure stack prefetch
|
||||||
|
cornerstoneTools.stackPrefetch.setConfiguration({
|
||||||
|
maxImagesToPrefetch: Infinity,
|
||||||
|
preserveExistingPool: false,
|
||||||
|
maxSimultaneousRequests: 20,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,76 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.4...@ohif/extension-debugging@0.1.5) (2020-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.3...@ohif/extension-debugging@0.1.4) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.3 (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.0...@ohif/extension-dicom-p10-downloader@0.1.1) (2020-06-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.0.2...@ohif/extension-dicom-p10-downloader@0.1.0) (2020-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* configuration to hook into XHR Error handling ([e96205d](https://github.com/OHIF/Viewers/commit/e96205de35e5bec14dc8a9a8509db3dd4e6ecdb6))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.0.2 (2020-04-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file. See
|
||||||
|
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-p10-downloader",
|
"name": "@ohif/extension-debugging",
|
||||||
"version": "0.1.0",
|
"version": "0.1.6",
|
||||||
"description": "OHIF extension for downloading DICOM P10 files",
|
"description": "OHIF extension for debugging.",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "OHIF/Viewers",
|
"repository": "OHIF/Viewers",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"detect-browser": "5.1.1",
|
||||||
"dicomweb-client": "^0.6.0",
|
"dicomweb-client": "^0.6.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"jszip": "^3.2.2"
|
"jszip": "^3.2.2"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.debugReportModalHeader {
|
||||||
|
padding: 10px 0 10px;
|
||||||
|
color: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-report-modal-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-report-modal-buttons-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { detect } from 'detect-browser';
|
||||||
|
import './DebugReportModal.css';
|
||||||
|
import { ToolbarButton } from '@ohif/ui';
|
||||||
|
|
||||||
|
const DubugReportModal = ({
|
||||||
|
viewports,
|
||||||
|
studies,
|
||||||
|
servers,
|
||||||
|
extensionManager,
|
||||||
|
mailTo,
|
||||||
|
debugModalMessage,
|
||||||
|
}) => {
|
||||||
|
const copyDebugDataToClipboard = () => {
|
||||||
|
const body = getEmailBody();
|
||||||
|
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
|
||||||
|
textArea.value = body;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEmailBody = () => {
|
||||||
|
let body = `Enter the description of your problem here: \n\n\n`;
|
||||||
|
|
||||||
|
body += `============= SESSION INFO =============\n\n`;
|
||||||
|
|
||||||
|
// App version
|
||||||
|
|
||||||
|
body += '== App ==\n';
|
||||||
|
body += `version\t${window.version}\n\n`;
|
||||||
|
|
||||||
|
// Extensions Versions
|
||||||
|
|
||||||
|
body += '== Extensions Versions ==\n';
|
||||||
|
|
||||||
|
const { registeredExtensionVesions } = extensionManager;
|
||||||
|
|
||||||
|
Object.keys(registeredExtensionVesions).forEach(extensionId => {
|
||||||
|
const version = registeredExtensionVesions[extensionId];
|
||||||
|
|
||||||
|
body += `${extensionId}\t${version}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
body += '\n';
|
||||||
|
|
||||||
|
// Browser Info
|
||||||
|
|
||||||
|
const browser = detect();
|
||||||
|
|
||||||
|
const { name, os, type, version } = browser;
|
||||||
|
|
||||||
|
body += '== Browser Info ==\n';
|
||||||
|
body += `name\t ${name}\n`;
|
||||||
|
body += `os\t ${os}\n`;
|
||||||
|
body += `type\t ${type}\n`;
|
||||||
|
body += `version\t ${version}\n\n`;
|
||||||
|
|
||||||
|
// Study URL
|
||||||
|
body += '== URL ==\n';
|
||||||
|
body += `URL\t ${window.location.href}\n\n`;
|
||||||
|
|
||||||
|
// Layout
|
||||||
|
const { numRows, numColumns, viewportSpecificData } = viewports;
|
||||||
|
|
||||||
|
body += '== Viewport Layout ==\n';
|
||||||
|
body += `Rows\t${numRows}\n`;
|
||||||
|
body += `Columns\t${numColumns}\n\n`;
|
||||||
|
body += '== Viewports ==\n';
|
||||||
|
|
||||||
|
Object.keys(viewportSpecificData).forEach(viewportIndex => {
|
||||||
|
const vsd = viewportSpecificData[viewportIndex];
|
||||||
|
|
||||||
|
const [row, column] = _viewportIndexToViewportPosition(
|
||||||
|
viewportIndex,
|
||||||
|
numColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
body += `[${row},${column}]\t${vsd.SeriesInstanceUID}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mailToFunction = () => {
|
||||||
|
const StudyInstanceUID = Object.keys(studies.studyData)[0];
|
||||||
|
|
||||||
|
const subject = encodeURI(`Issue with Study: ${StudyInstanceUID}`);
|
||||||
|
|
||||||
|
let body = getEmailBody();
|
||||||
|
|
||||||
|
// TODO Text dump of rest of stuff.
|
||||||
|
|
||||||
|
body = encodeURI(body);
|
||||||
|
|
||||||
|
window.location.href = `mailto:${mailTo}?subject=${subject}&body=${body}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="debug-report-modal-container">
|
||||||
|
{debugModalMessage ? <p>{debugModalMessage}</p> : null}
|
||||||
|
<div className="debug-report-modal-buttons-container">
|
||||||
|
{mailTo ? (
|
||||||
|
<div>
|
||||||
|
<ToolbarButton
|
||||||
|
label={'Send Bug Report'}
|
||||||
|
onClick={mailToFunction}
|
||||||
|
icon={'envelope-square'}
|
||||||
|
isActive={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div>
|
||||||
|
<ToolbarButton
|
||||||
|
label={'Copy To Clipboard'}
|
||||||
|
onClick={copyDebugDataToClipboard}
|
||||||
|
icon={'clipboard'}
|
||||||
|
isActive={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
{getAppVersion()}
|
||||||
|
{getExtensionVersions(extensionManager)}
|
||||||
|
{getBrowserInfo()}
|
||||||
|
{getCurrentStudyUrl()}
|
||||||
|
{getLayout(viewports)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAppVersion = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<tr>
|
||||||
|
<th className="debugReportModalHeader">App</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Version</td>
|
||||||
|
<td>{window.version}</td>
|
||||||
|
</tr>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentStudyUrl = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<tr>
|
||||||
|
<th className="debugReportModalHeader">URL</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>URL</td>
|
||||||
|
<td>{window.location.href}</td>
|
||||||
|
</tr>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExtensionVersions = extensionManager => {
|
||||||
|
const { registeredExtensionVesions } = extensionManager;
|
||||||
|
|
||||||
|
const lineItems = Object.keys(registeredExtensionVesions).map(extensionId => {
|
||||||
|
const version = registeredExtensionVesions[extensionId];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{extensionId}</td>
|
||||||
|
<td>{version}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<th className="debugReportModalHeader">Extensions</th>
|
||||||
|
{lineItems}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLayout = viewports => {
|
||||||
|
const { numRows, numColumns } = viewports;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<tr>
|
||||||
|
<th className="debugReportModalHeader">Viewports</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Layout</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Rows</td>
|
||||||
|
<td>{numRows}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Columns</td>
|
||||||
|
<td>{numColumns}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>SeriesInstanceUIDs</th>
|
||||||
|
</tr>
|
||||||
|
{getSeriesInstanceUIDsPerRow(viewports)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBrowserInfo = () => {
|
||||||
|
const browser = detect();
|
||||||
|
|
||||||
|
const { name, os, type, version } = browser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<tr>
|
||||||
|
<th className="debugReportModalHeader">Platform</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>name</td>
|
||||||
|
<td>{name}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>os</td>
|
||||||
|
<td>{os}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>type</td>
|
||||||
|
<td>{type}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>version</td>
|
||||||
|
<td>{version}</td>
|
||||||
|
</tr>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSeriesInstanceUIDsPerRow = viewports => {
|
||||||
|
const { viewportSpecificData, numColumns } = viewports;
|
||||||
|
|
||||||
|
// NOTE viewportSpecificData is actually an object with numerical keys.
|
||||||
|
return Object.keys(viewportSpecificData).map(viewportIndex => {
|
||||||
|
const vsd = viewportSpecificData[viewportIndex];
|
||||||
|
|
||||||
|
const [row, column] = _viewportIndexToViewportPosition(
|
||||||
|
viewportIndex,
|
||||||
|
numColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{`[${row},${column}]`}</td>
|
||||||
|
<td>{vsd.SeriesInstanceUID}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const _viewportIndexToViewportPosition = (viewportIndex, numColumns) => {
|
||||||
|
const row = Math.floor(viewportIndex / numColumns);
|
||||||
|
const column = viewportIndex % numColumns;
|
||||||
|
|
||||||
|
return [row, column];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DubugReportModal;
|
||||||
+74
-5
@@ -1,19 +1,23 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import {
|
import {
|
||||||
save,
|
save,
|
||||||
|
upload,
|
||||||
getDicomWebClientFromContext,
|
getDicomWebClientFromContext,
|
||||||
|
getStudyInstanceUIDFromStudies,
|
||||||
getSOPInstanceReferenceFromActiveViewport,
|
getSOPInstanceReferenceFromActiveViewport,
|
||||||
getSOPInstanceReferencesFromViewports,
|
getSOPInstanceReferencesFromViewports,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import _downloadAndZip from './downloadAndZip';
|
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
|
||||||
|
import DebugReportModal from './DebugReportModal';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import state from './state';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
utils: {
|
utils: { Queue },
|
||||||
Queue,
|
|
||||||
},
|
|
||||||
} = OHIF;
|
} = OHIF;
|
||||||
|
|
||||||
export function getCommands(context) {
|
export function getCommands(context, servicesManager, extensionManager) {
|
||||||
const queue = new Queue(1);
|
const queue = new Queue(1);
|
||||||
const actions = {
|
const actions = {
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +45,14 @@ export function getCommands(context) {
|
|||||||
listOfUIDs
|
listOfUIDs
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
downloadAndZipStudy({ servers, studies, progress }) {
|
||||||
|
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||||
|
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
|
||||||
|
return save(
|
||||||
|
_downloadAndZip(dicomWebClient, listOfUIDs, { progress }),
|
||||||
|
listOfUIDs
|
||||||
|
);
|
||||||
|
},
|
||||||
downloadAndZipSeriesOnViewports({ servers, viewports, progress }) {
|
downloadAndZipSeriesOnViewports({ servers, viewports, progress }) {
|
||||||
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||||
const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports);
|
const listOfUIDs = getSOPInstanceReferencesFromViewports(viewports);
|
||||||
@@ -57,13 +69,65 @@ export function getCommands(context) {
|
|||||||
listOfUIDs
|
listOfUIDs
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
downloadAndUploadStudy({ servers, studies, progress, serverConfig }) {
|
||||||
|
const dicomWebClient = getDicomWebClientFromContext(context, servers);
|
||||||
|
const listOfUIDs = getStudyInstanceUIDFromStudies(studies);
|
||||||
|
return upload(
|
||||||
|
downloadInstances(dicomWebClient, listOfUIDs, { progress }),
|
||||||
|
/**
|
||||||
|
* serverConfig is an object with the values used to create a new
|
||||||
|
* instance of DICOMwebClient.
|
||||||
|
*
|
||||||
|
* Basic Structure:
|
||||||
|
*
|
||||||
|
* const config = {
|
||||||
|
* url,
|
||||||
|
* headers,
|
||||||
|
* errorInterceptor
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const dicomWeb = new api.DICOMwebClient(config);
|
||||||
|
*/
|
||||||
|
serverConfig
|
||||||
|
);
|
||||||
|
},
|
||||||
|
openDebugInfoModal({ viewports, studies, servers }) {
|
||||||
|
const { UIModalService } = servicesManager.services;
|
||||||
|
|
||||||
|
const WrappedDebugReportModal = function() {
|
||||||
|
return (
|
||||||
|
<DebugReportModal
|
||||||
|
viewports={viewports}
|
||||||
|
studies={studies}
|
||||||
|
servers={servers}
|
||||||
|
extensionManager={extensionManager}
|
||||||
|
mailTo={state.mailTo}
|
||||||
|
debugModalMessage={state.debugModalMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
UIModalService.show({
|
||||||
|
content: WrappedDebugReportModal,
|
||||||
|
title: `Debugging Information`,
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
|
openDebugInfoModal: {
|
||||||
|
commandFn: actions.openDebugInfoModal,
|
||||||
|
storeContexts: ['viewports', 'servers', 'studies'],
|
||||||
|
},
|
||||||
downloadAndZip: {
|
downloadAndZip: {
|
||||||
commandFn: queue.bindSafe(actions.downloadAndZip, error),
|
commandFn: queue.bindSafe(actions.downloadAndZip, error),
|
||||||
storeContexts: ['servers'],
|
storeContexts: ['servers'],
|
||||||
},
|
},
|
||||||
|
downloadAndZipStudy: {
|
||||||
|
commandFn: queue.bindSafe(actions.downloadAndZipStudy, error),
|
||||||
|
storeContexts: ['servers', 'studies'],
|
||||||
|
options: { progress },
|
||||||
|
},
|
||||||
downloadAndZipSeriesOnViewports: {
|
downloadAndZipSeriesOnViewports: {
|
||||||
commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error),
|
commandFn: queue.bindSafe(actions.downloadAndZipSeriesOnViewports, error),
|
||||||
storeContexts: ['servers', 'viewports'],
|
storeContexts: ['servers', 'viewports'],
|
||||||
@@ -77,6 +141,11 @@ export function getCommands(context) {
|
|||||||
storeContexts: ['servers', 'viewports'],
|
storeContexts: ['servers', 'viewports'],
|
||||||
options: { progress },
|
options: { progress },
|
||||||
},
|
},
|
||||||
|
downloadAndUploadStudy: {
|
||||||
|
commandFn: queue.bindSafe(actions.downloadAndUploadStudy, error),
|
||||||
|
storeContexts: ['servers', 'studies'],
|
||||||
|
options: { progress },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
+23
-8
@@ -79,13 +79,7 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
|
|||||||
if (dicomWebClient instanceof api.DICOMwebClient) {
|
if (dicomWebClient instanceof api.DICOMwebClient) {
|
||||||
const settings = buildSettings(listOfUIDs, options);
|
const settings = buildSettings(listOfUIDs, options);
|
||||||
const { compression } = settings.tasks;
|
const { compression } = settings.tasks;
|
||||||
// Register user-provided progress handler as a task list observer
|
const buffers = await downloadBuffers(settings, dicomWebClient);
|
||||||
progressUtils.addObserver(settings.taskList, settings.options.progress);
|
|
||||||
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
|
|
||||||
// Reject promise from compression task on download failure
|
|
||||||
compression.deferred.reject(error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
compression.deferred.resolve(zipAll(buffers, settings));
|
compression.deferred.resolve(zipAll(buffers, settings));
|
||||||
const url = await compression.deferred.promise;
|
const url = await compression.deferred.promise;
|
||||||
return url;
|
return url;
|
||||||
@@ -93,6 +87,27 @@ async function downloadAndZip(dicomWebClient, listOfUIDs, options) {
|
|||||||
throw new Error('A valid DICOM Web Client instance is expected');
|
throw new Error('A valid DICOM Web Client instance is expected');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadInstances(dicomWebClient, listOfUIDs, options) {
|
||||||
|
if (dicomWebClient instanceof api.DICOMwebClient) {
|
||||||
|
const settings = buildSettings(listOfUIDs, options);
|
||||||
|
const buffers = await downloadBuffers(settings, dicomWebClient);
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
throw new Error('A valid DICOM Web Client instance is expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadBuffers(settings, dicomWebClient) {
|
||||||
|
const { compression } = settings.tasks;
|
||||||
|
// Register user-provided progress handler as a task list observer
|
||||||
|
progressUtils.addObserver(settings.taskList, settings.options.progress);
|
||||||
|
const buffers = await downloadAll(dicomWebClient, settings).catch(error => {
|
||||||
|
// Reject promise from compression task on download failure
|
||||||
|
compression.deferred.reject(error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils
|
* Utils
|
||||||
*/
|
*/
|
||||||
@@ -241,4 +256,4 @@ async function download(
|
|||||||
* Exports
|
* Exports
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { downloadAndZip as default, downloadAndZip };
|
export { downloadAndZip as default, downloadAndZip, downloadInstances };
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { getDicomWebClientFromConfig } from './utils';
|
||||||
|
import { getCommands } from './commandsModule';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
import toolbarModule from './toolbarModule';
|
||||||
|
import state from './state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Globals
|
||||||
|
*/
|
||||||
|
|
||||||
|
const sharedContext = {
|
||||||
|
dicomWebClient: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
*/
|
||||||
|
id: 'dicom-p10-downloader',
|
||||||
|
version,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LIFECYCLE HOOKS
|
||||||
|
*/
|
||||||
|
|
||||||
|
preRegistration({ appConfig, configuration }) {
|
||||||
|
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
|
||||||
|
if (dicomWebClient) {
|
||||||
|
sharedContext.dicomWebClient = dicomWebClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration) {
|
||||||
|
if (configuration.mailTo) {
|
||||||
|
state.mailTo = configuration.mailTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.debugModalMessage) {
|
||||||
|
state.debugModalMessage = configuration.debugModalMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MODULE GETTERS
|
||||||
|
*/
|
||||||
|
|
||||||
|
getCommandsModule({ servicesManager, extensionManager }) {
|
||||||
|
return getCommands(sharedContext, servicesManager, extensionManager);
|
||||||
|
},
|
||||||
|
|
||||||
|
getToolbarModule() {
|
||||||
|
return toolbarModule;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
const state = { mailTo: undefined, message: undefined };
|
||||||
|
|
||||||
|
export default state;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const TOOLBAR_BUTTON_TYPES = {
|
||||||
|
COMMAND: 'command',
|
||||||
|
};
|
||||||
|
|
||||||
|
const definitions = [
|
||||||
|
{
|
||||||
|
id: 'Debug Info',
|
||||||
|
label: 'Debug Info',
|
||||||
|
icon: 'cog',
|
||||||
|
//
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'openDebugInfoModal',
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
definitions,
|
||||||
|
defaultContext: 'VIEWER',
|
||||||
|
};
|
||||||
@@ -100,11 +100,59 @@ function save(promise, listOfUIDs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upload(promise, serverConfig) {
|
||||||
|
return Promise.resolve(promise)
|
||||||
|
.then(async instances => {
|
||||||
|
const instancesAmount = instances.length;
|
||||||
|
OHIF.log.info(`Uploading study to ${serverConfig.url}`);
|
||||||
|
OHIF.log.info(
|
||||||
|
`${instancesAmount} instances are being uploaded. Don't close your browser.`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dicomWeb = new api.DICOMwebClient(serverConfig);
|
||||||
|
let progress = 0;
|
||||||
|
|
||||||
|
const getProgress = () => {
|
||||||
|
return ((progress * 100) / instancesAmount).toFixed();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const instance of instances) {
|
||||||
|
const options = {
|
||||||
|
datasets: [instance],
|
||||||
|
};
|
||||||
|
|
||||||
|
await dicomWeb.storeInstances(options);
|
||||||
|
|
||||||
|
progress++;
|
||||||
|
|
||||||
|
OHIF.log.info(`Progress: ${getProgress()}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
OHIF.log.info('Successfully uploaded!');
|
||||||
|
} catch (error) {
|
||||||
|
OHIF.log.error(`Failed to upload: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
OHIF.log.error(`Failed to upload: ${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStudyInstanceUIDFromStudies(studies) {
|
||||||
|
return Object.keys(Object(Object(studies).studyData)).slice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
save,
|
save,
|
||||||
|
upload,
|
||||||
validDicomUid,
|
validDicomUid,
|
||||||
getDicomWebClientFromConfig,
|
getDicomWebClientFromConfig,
|
||||||
getDicomWebClientFromContext,
|
getDicomWebClientFromContext,
|
||||||
|
getStudyInstanceUIDFromStudies,
|
||||||
getSOPInstanceReferenceFromActiveViewport,
|
getSOPInstanceReferenceFromActiveViewport,
|
||||||
getSOPInstanceReferencesFromViewports,
|
getSOPInstanceReferencesFromViewports,
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,87 @@
|
|||||||
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.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.7...@ohif/extension-dicom-html@1.2.8) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.6...@ohif/extension-dicom-html@1.2.7) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.5...@ohif/extension-dicom-html@1.2.6) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.4...@ohif/extension-dicom-html@1.2.5) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.3...@ohif/extension-dicom-html@1.2.4) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.2...@ohif/extension-dicom-html@1.2.3) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.1...@ohif/extension-dicom-html@1.2.2) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.0...@ohif/extension-dicom-html@1.2.1) (2020-04-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.2...@ohif/extension-dicom-html@1.2.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.1...@ohif/extension-dicom-html@1.1.2) (2020-04-02)
|
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.1...@ohif/extension-dicom-html@1.1.2) (2020-04-02)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-html",
|
"name": "@ohif/extension-dicom-html",
|
||||||
"version": "1.1.2",
|
"version": "1.2.8",
|
||||||
"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.10.1",
|
"dcmjs": "0.16.3",
|
||||||
"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"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
import TypedArrayProp from './TypedArrayProp';
|
import TypedArrayProp from './TypedArrayProp';
|
||||||
|
|
||||||
import './DicomHtmlViewport.css';
|
import './DicomHtmlViewport.css';
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ const OHIFDicomHtmlSopClassHandler = {
|
|||||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
const metadata = instance.getData().metadata;
|
||||||
|
const {
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDate,
|
||||||
|
SeriesTime,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'html',
|
plugin: 'html',
|
||||||
Modality: 'SR',
|
Modality: 'SR',
|
||||||
@@ -31,6 +39,11 @@ const OHIFDicomHtmlSopClassHandler = {
|
|||||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
|
SeriesDescription,
|
||||||
|
metadata,
|
||||||
|
SeriesDate,
|
||||||
|
SeriesTime,
|
||||||
|
SeriesNumber,
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
|
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import('./OHIFDicomHtmlViewport');
|
return import('./OHIFDicomHtmlViewport');
|
||||||
@@ -18,6 +19,7 @@ export default {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'html',
|
id: 'html',
|
||||||
|
version,
|
||||||
|
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return OHIFDicomHtmlViewport;
|
return OHIFDicomHtmlViewport;
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
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.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.1...@ohif/extension-dicom-microscopy@0.51.2) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.0...@ohif/extension-dicom-microscopy@0.51.1) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
|
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.8...@ohif/extension-dicom-microscopy@0.50.9) (2020-04-02)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-microscopy",
|
"name": "@ohif/extension-dicom-microscopy",
|
||||||
"version": "0.50.9",
|
"version": "0.51.3",
|
||||||
"description": "OHIF extension for Dicom Microscopy",
|
"description": "OHIF extension for Dicom Microscopy",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ const DicomMicroscopySopClassHandler = {
|
|||||||
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
const metadata = instance.getData().metadata;
|
||||||
|
const {
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
ContentDate,
|
||||||
|
ContentTime,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
// Note: We are passing the dicomweb client into each viewport!
|
// Note: We are passing the dicomweb client into each viewport!
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -22,6 +30,11 @@ const DicomMicroscopySopClassHandler = {
|
|||||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||||
|
SeriesTime: ContentTime,
|
||||||
|
SeriesNumber,
|
||||||
|
metadata,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
{this.state.error ? (
|
{this.state.error ? (
|
||||||
<h2>{JSON.stringify(this.state.error)}</h2>
|
<h2>{JSON.stringify(this.state.error)}</h2>
|
||||||
) : (
|
) : (
|
||||||
<div style={style} ref={this.container} />
|
<div style={style} ref={this.container} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
|
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import('./DicomMicroscopyViewport');
|
return import('./DicomMicroscopyViewport');
|
||||||
@@ -18,6 +19,7 @@ export default {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'microscopy',
|
id: 'microscopy',
|
||||||
|
version,
|
||||||
|
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return DicomMicroscopyViewport;
|
return DicomMicroscopyViewport;
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
||||||
|
|
||||||
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.0.2...@ohif/extension-dicom-p10-downloader@0.1.0) (2020-04-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* configuration to hook into XHR Error handling ([e96205d](https://github.com/OHIF/Viewers/commit/e96205de35e5bec14dc8a9a8509db3dd4e6ecdb6))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 0.0.2 (2020-04-15)
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See
|
|
||||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { getDicomWebClientFromConfig } from './utils';
|
|
||||||
import { getCommands } from './commandsModule';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constants
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Globals
|
|
||||||
*/
|
|
||||||
|
|
||||||
const sharedContext = {
|
|
||||||
dicomWebClient: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* Only required property. Should be a unique value across all extensions.
|
|
||||||
*/
|
|
||||||
id: 'dicom-p10-downloader',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LIFECYCLE HOOKS
|
|
||||||
*/
|
|
||||||
|
|
||||||
preRegistration({ appConfig }) {
|
|
||||||
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
|
|
||||||
if (dicomWebClient) {
|
|
||||||
sharedContext.dicomWebClient = dicomWebClient;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MODULE GETTERS
|
|
||||||
*/
|
|
||||||
|
|
||||||
getCommandsModule() {
|
|
||||||
return getCommands(sharedContext);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -3,6 +3,30 @@
|
|||||||
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.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.4...@ohif/extension-dicom-pdf@1.0.5) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.3...@ohif/extension-dicom-pdf@1.0.4) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
|
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
**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.3",
|
"version": "1.0.6",
|
||||||
"description": "OHIF extension for Dicom PDF",
|
"description": "OHIF extension for Dicom PDF",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ const OHIFDicomPDFSopClassHandler = {
|
|||||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
|
const metadata = instance.getData().metadata;
|
||||||
|
const {
|
||||||
|
ContentDate,
|
||||||
|
ContentTime,
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesNumber,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'pdf',
|
plugin: 'pdf',
|
||||||
Modality: 'DOC',
|
Modality: 'DOC',
|
||||||
@@ -21,6 +29,11 @@ const OHIFDicomPDFSopClassHandler = {
|
|||||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
|
SeriesDescription,
|
||||||
|
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||||
|
SeriesTime: ContentTime,
|
||||||
|
SeriesNumber,
|
||||||
|
metadata,
|
||||||
authorizationHeaders: authorizationHeaders,
|
authorizationHeaders: authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
const Component = React.lazy(() => {
|
const Component = React.lazy(() => {
|
||||||
return import('./ConnectedOHIFDicomPDFViewer');
|
return import('./ConnectedOHIFDicomPDFViewer');
|
||||||
@@ -18,6 +19,7 @@ export default {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'pdf',
|
id: 'pdf',
|
||||||
|
version,
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return ConnectedOHIFDicomPDFViewer;
|
return ConnectedOHIFDicomPDFViewer;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,212 @@
|
|||||||
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.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.6...@ohif/extension-dicom-rt@0.4.7) (2020-10-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.5...@ohif/extension-dicom-rt@0.4.6) (2020-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.4...@ohif/extension-dicom-rt@0.4.5) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.3...@ohif/extension-dicom-rt@0.4.4) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.2...@ohif/extension-dicom-rt@0.4.3) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.1...@ohif/extension-dicom-rt@0.4.2) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.0...@ohif/extension-dicom-rt@0.4.1) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.3.0...@ohif/extension-dicom-rt@0.4.0) (2020-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.7...@ohif/extension-dicom-rt@0.3.0) (2020-08-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Add support for POINT and OPEN_PLANAR for RT ([0e87ab3](https://github.com/OHIF/Viewers/commit/0e87ab37c29fcf9af74bbcefca854c6e6b8707bc))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.6...@ohif/extension-dicom-rt@0.2.7) (2020-08-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.5...@ohif/extension-dicom-rt@0.2.6) (2020-08-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.4...@ohif/extension-dicom-rt@0.2.5) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.3...@ohif/extension-dicom-rt@0.2.4) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.2...@ohif/extension-dicom-rt@0.2.3) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.1...@ohif/extension-dicom-rt@0.2.2) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.2.0...@ohif/extension-dicom-rt@0.2.1) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.4...@ohif/extension-dicom-rt@0.2.0) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.3...@ohif/extension-dicom-rt@0.1.4) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.2...@ohif/extension-dicom-rt@0.1.3) (2020-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.1...@ohif/extension-dicom-rt@0.1.2) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.1.0...@ohif/extension-dicom-rt@0.1.1) (2020-04-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.0.2...@ohif/extension-dicom-rt@0.1.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 0.0.2 (2020-04-09)
|
## 0.0.2 (2020-04-09)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-rt",
|
"name": "@ohif/extension-dicom-rt",
|
||||||
"version": "0.0.2",
|
"version": "0.4.7",
|
||||||
"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",
|
||||||
@@ -30,11 +30,12 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "^4.0.9",
|
"cornerstone-tools": "^4.20.1",
|
||||||
"dcmjs": "^0.8.3",
|
"dcmjs": "0.16.3",
|
||||||
"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",
|
||||||
|
"react-tooltip": "4.2.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core';
|
import { MODULE_TYPES, utils, DICOMWeb } from '@ohif/core';
|
||||||
import loadRTStruct from './loadRTStruct';
|
import loadRTStruct from './loadRTStruct';
|
||||||
|
import getSourceDisplaySet from './getSourceDisplaySet';
|
||||||
|
|
||||||
import id from './id';
|
import id from './id';
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
id: 'OHIFDicomRTStructSopClassHandler',
|
id: 'OHIFDicomRTStructSopClassHandler',
|
||||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||||
sopClassUIDs,
|
sopClassUIDs,
|
||||||
getDisplaySetFromSeries: function (
|
getDisplaySetFromSeries: function(
|
||||||
series,
|
series,
|
||||||
study,
|
study,
|
||||||
dicomWebClient,
|
dicomWebClient,
|
||||||
@@ -26,6 +27,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
const {
|
const {
|
||||||
SeriesDate,
|
SeriesDate,
|
||||||
SeriesTime,
|
SeriesTime,
|
||||||
|
SeriesNumber,
|
||||||
SeriesDescription,
|
SeriesDescription,
|
||||||
FrameOfReferenceUID,
|
FrameOfReferenceUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
@@ -52,7 +54,9 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
SeriesDate,
|
SeriesDate,
|
||||||
SeriesTime,
|
SeriesTime,
|
||||||
|
SeriesNumber,
|
||||||
SeriesDescription,
|
SeriesDescription,
|
||||||
|
metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!metadata.ReferencedSeriesSequence) {
|
if (!metadata.ReferencedSeriesSequence) {
|
||||||
@@ -67,13 +71,18 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
|
rtStructDisplaySet.getSourceDisplaySet = function(studies) {
|
||||||
|
return getSourceDisplaySet(studies, rtStructDisplaySet);
|
||||||
|
};
|
||||||
|
|
||||||
|
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||||
return loadRTStruct(
|
return loadRTStruct(
|
||||||
rtStructDisplaySet,
|
rtStructDisplaySet,
|
||||||
referencedDisplaySet,
|
referencedDisplaySet,
|
||||||
studies
|
studies
|
||||||
).catch(error => {
|
).catch(error => {
|
||||||
rtStructDisplaySet.isLoaded = false;
|
rtStructDisplaySet.isLoaded = false;
|
||||||
|
rtStructDisplaySet.loadError = true;
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,12 +12,17 @@ import StructureSetItem from '../StructureSetItem/StructureSetItem';
|
|||||||
import RTPanelSettings from '../RTSettings/RTSettings';
|
import RTPanelSettings from '../RTSettings/RTSettings';
|
||||||
import PanelSection from '../PanelSection/PanelSection';
|
import PanelSection from '../PanelSection/PanelSection';
|
||||||
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
|
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
|
||||||
|
import TOOL_NAMES from '../../utils/toolNames';
|
||||||
|
|
||||||
|
const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;
|
||||||
|
|
||||||
const { studyMetadataManager } = utils;
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
const refreshViewport = () => {
|
const refreshViewport = () => {
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||||
cornerstone.updateImage(enabledElement.element);
|
if (enabledElement.image) {
|
||||||
|
cornerstone.updateImage(enabledElement.element);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,7 +36,18 @@ const refreshViewport = () => {
|
|||||||
* @param {number} props.isOpen - isOpen
|
* @param {number} props.isOpen - isOpen
|
||||||
* @returns component
|
* @returns component
|
||||||
*/
|
*/
|
||||||
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => {
|
const RTPanel = ({
|
||||||
|
studies,
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
isOpen,
|
||||||
|
onContourItemClick,
|
||||||
|
activeContexts = [],
|
||||||
|
contexts = {},
|
||||||
|
}) => {
|
||||||
|
const isVTK = () => activeContexts.includes(contexts.VTK);
|
||||||
|
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
||||||
|
|
||||||
const [selectedContour, setSelectedContour] = useState();
|
const [selectedContour, setSelectedContour] = useState();
|
||||||
const DEFAULT_SET_INDEX = 0;
|
const DEFAULT_SET_INDEX = 0;
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
@@ -65,7 +81,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
document.removeEventListener(
|
||||||
|
'extensiondicomrtrtloaded',
|
||||||
|
updateStructureSets
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -78,7 +97,9 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
activeViewport.SeriesInstanceUID
|
activeViewport.SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
activeViewport.StudyInstanceUID
|
||||||
|
);
|
||||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||||
Modality: 'RTSTRUCT',
|
Modality: 'RTSTRUCT',
|
||||||
@@ -89,7 +110,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
setState({
|
setState({
|
||||||
referencedDisplaysets,
|
referencedDisplaysets,
|
||||||
selectedSet: defaultSet,
|
selectedSet: defaultSet,
|
||||||
sets: viewportSets
|
sets: viewportSets,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(DEFAULT_STATE);
|
setState(DEFAULT_STATE);
|
||||||
@@ -105,7 +126,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
setShowSettings(showSettings && !isOpen);
|
setShowSettings(showSettings && !isOpen);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const toContourItem = ({ ROINumber, ROIName, RTROIObservations, colorArray, visible }, loadedSet) => {
|
const toContourItem = (
|
||||||
|
{ ROINumber, ROIName, RTROIObservations, colorArray, visible, isSupported },
|
||||||
|
loadedSet
|
||||||
|
) => {
|
||||||
let interpretedType = '';
|
let interpretedType = '';
|
||||||
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
|
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
|
||||||
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
|
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
|
||||||
@@ -116,36 +140,60 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
<StructureSetItem
|
<StructureSetItem
|
||||||
key={ROINumber}
|
key={ROINumber}
|
||||||
selected={isSameContour}
|
selected={isSameContour}
|
||||||
|
isDisabled={!isSupported}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedContour(isSameContour ? null : ROINumber);
|
setSelectedContour(isSameContour ? null : ROINumber);
|
||||||
|
|
||||||
const enabledElements = cornerstone.getEnabledElements();
|
if (isCornerstone()) {
|
||||||
const element = enabledElements[activeIndex].element;
|
const enabledElements = cornerstone.getEnabledElements();
|
||||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
const element = enabledElements[activeIndex].element;
|
||||||
|
const stackToolState = cornerstoneTools.getToolState(
|
||||||
|
element,
|
||||||
|
'stack'
|
||||||
|
);
|
||||||
|
|
||||||
if (!toolState) {
|
if (!stackToolState) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIds = stackToolState.data[0].imageIds;
|
||||||
|
|
||||||
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
|
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
||||||
|
state.selectedSet.SeriesInstanceUID,
|
||||||
|
ROINumber,
|
||||||
|
imageIds
|
||||||
|
);
|
||||||
|
|
||||||
|
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||||
|
const imageIdSpecificToolState = toolState[imageId];
|
||||||
|
|
||||||
|
const rtstructData =
|
||||||
|
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];
|
||||||
|
|
||||||
|
const specificData = rtstructData.data.find(
|
||||||
|
rtData => rtData.ROINumber === ROINumber
|
||||||
|
);
|
||||||
|
|
||||||
|
specificData.highlight = true;
|
||||||
|
|
||||||
|
const frameIndex = imageIds.indexOf(imageId);
|
||||||
|
const SOPInstanceUID = cornerstone.metaData.get(
|
||||||
|
'SOPInstanceUID',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
const StudyInstanceUID = cornerstone.metaData.get(
|
||||||
|
'StudyInstanceUID',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
|
||||||
|
onContourItemClick({
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameIndex,
|
||||||
|
activeViewportIndex: activeIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageIds = toolState.data[0].imageIds;
|
|
||||||
|
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
|
||||||
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
|
||||||
state.selectedSet.SeriesInstanceUID,
|
|
||||||
ROINumber,
|
|
||||||
imageIds
|
|
||||||
);
|
|
||||||
|
|
||||||
const frameIndex = imageIds.indexOf(imageId);
|
|
||||||
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
|
|
||||||
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
|
|
||||||
|
|
||||||
onContourItemClick({
|
|
||||||
StudyInstanceUID,
|
|
||||||
SOPInstanceUID,
|
|
||||||
frameIndex,
|
|
||||||
activeViewportIndex: activeIndex
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
label={`${ROIName} ${interpretedType}`}
|
label={`${ROIName} ${interpretedType}`}
|
||||||
index={ROINumber}
|
index={ROINumber}
|
||||||
@@ -153,7 +201,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
visible={visible}
|
visible={visible}
|
||||||
onVisibilityChange={() => {
|
onVisibilityChange={() => {
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
module.setters.toggleROIContour(state.selectedSet.SeriesInstanceUID, ROINumber);
|
module.setters.toggleROIContour(
|
||||||
|
state.selectedSet.SeriesInstanceUID,
|
||||||
|
ROINumber
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -189,44 +240,70 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
onClick={() => setShowSettings(true)}
|
onClick={() => setShowSettings(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
|
{!state.referencedDisplaysets.length && (
|
||||||
{state.referencedDisplaysets.map(displaySet => {
|
<LoadingIndicator expand height="70px" width="70px" />
|
||||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
)}
|
||||||
|
{state.sets &&
|
||||||
|
state.referencedDisplaysets.map(displaySet => {
|
||||||
|
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||||
|
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
const loadedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
const loadedSet = sets.find(
|
||||||
return (
|
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||||
<PanelSection
|
);
|
||||||
key={SeriesInstanceUID}
|
return (
|
||||||
title={metadata.StructureSetLabel}
|
<PanelSection
|
||||||
loading={!isLoaded || !loadedSet}
|
key={SeriesInstanceUID}
|
||||||
visible={isLoaded && loadedSet.visible}
|
title={metadata.StructureSetLabel}
|
||||||
hideVisibleButton={!isLoaded}
|
loading={!isLoaded || !loadedSet}
|
||||||
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID}
|
visible={isLoaded && loadedSet && loadedSet.visible}
|
||||||
onVisibilityChange={() => {
|
hideVisibleButton={!isLoaded}
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
expanded={
|
||||||
module.setters.toggleStructureSet(SeriesInstanceUID);
|
isLoaded &&
|
||||||
}}
|
loadedSet.SeriesInstanceUID ===
|
||||||
onExpandChange={async () => {
|
state.selectedSet.SeriesInstanceUID
|
||||||
if (!isLoaded) {
|
|
||||||
await displaySet.load(viewports[activeIndex], studies);
|
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
|
||||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
|
||||||
const selectedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
|
||||||
setState(state => ({ ...state, selectedSet, sets }));
|
|
||||||
}
|
}
|
||||||
}}
|
onVisibilityChange={newVisibility => {
|
||||||
>
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
<ScrollableArea>
|
|
||||||
<TableList headless>
|
if (newVisibility) {
|
||||||
{isLoaded && loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
|
||||||
</TableList>
|
} else {
|
||||||
</ScrollableArea>
|
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
|
||||||
</PanelSection>
|
}
|
||||||
);
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
})}
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
setState(state => ({ ...state, sets }));
|
||||||
|
refreshViewport();
|
||||||
|
}}
|
||||||
|
onExpandChange={async () => {
|
||||||
|
if (!isLoaded) {
|
||||||
|
await displaySet.load(viewports[activeIndex], studies);
|
||||||
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
const selectedSet = sets.find(
|
||||||
|
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||||
|
);
|
||||||
|
setState(state => ({ ...state, selectedSet, sets }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScrollableArea>
|
||||||
|
<TableList headless>
|
||||||
|
{isLoaded &&
|
||||||
|
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||||
|
</TableList>
|
||||||
|
</ScrollableArea>
|
||||||
|
</PanelSection>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,11 +22,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcmrt-structure-set-item.selected .item-actions {
|
|
||||||
height: 35px;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dcmrt-structure-set-item .item-actions {
|
.dcmrt-structure-set-item .item-actions {
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
background-color: var(--ui-gray-darker);
|
background-color: var(--ui-gray-darker);
|
||||||
@@ -47,7 +42,8 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcmrt-structure-set-item .item-actions .btnAction:hover, .dcmrt-structure-set-item .item-actions .btnAction:active {
|
.dcmrt-structure-set-item .item-actions .btnAction:hover,
|
||||||
|
.dcmrt-structure-set-item .item-actions .btnAction:active {
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,14 +57,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dcmrt-structure-set-item .item-label {
|
.dcmrt-structure-set-item .item-label {
|
||||||
overflow: hidden;
|
|
||||||
max-width: calc(100% - 15px); /* calc(100% - 50px); 20px = eye icon */
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmrt-structure-set-item .item-label span {
|
||||||
|
overflow-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: calc(100% - 40px); /* calc(100% - 50px); 20px = eye icon */
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcmrt-structure-set-item .item-label .eye-icon {
|
.dcmrt-structure-set-item .item-label .eye-icon {
|
||||||
@@ -83,3 +82,12 @@
|
|||||||
.dcmrt-structure-set-item .item-label .eye-icon.--visible {
|
.dcmrt-structure-set-item .item-label .eye-icon.--visible {
|
||||||
color: var(--default-color);
|
color: var(--default-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dcmrt-structure-set-item.isDisabled .item-color-section {
|
||||||
|
background-color: #e29e4a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmrt-structure-set-item.isDisabled .item-label {
|
||||||
|
color: var(--text-disabled-color);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TableListItem, Icon } from '@ohif/ui';
|
import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
|
||||||
|
import ReactTooltip from 'react-tooltip';
|
||||||
|
|
||||||
import './StructureSetItem.css';
|
import './StructureSetItem.css';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ ColoredCircle.propTypes = {
|
|||||||
const StructureSetItem = ({
|
const StructureSetItem = ({
|
||||||
index,
|
index,
|
||||||
label,
|
label,
|
||||||
|
isDisabled,
|
||||||
onClick,
|
onClick,
|
||||||
itemClass,
|
itemClass,
|
||||||
color,
|
color,
|
||||||
@@ -28,20 +30,58 @@ const StructureSetItem = ({
|
|||||||
selected = false,
|
selected = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isVisible, setIsVisible] = useState(visible);
|
const [isVisible, setIsVisible] = useState(visible);
|
||||||
return (
|
|
||||||
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
|
useEffect(() => {
|
||||||
<TableListItem
|
setIsVisible(visible);
|
||||||
key={index}
|
}, [visible]);
|
||||||
itemKey={index}
|
|
||||||
itemIndex={index}
|
let dcmrtClassNames = `dcmrt-structure-set-item`;
|
||||||
itemClass={itemClass}
|
|
||||||
itemMeta={<ColoredCircle color={color} />}
|
if (selected) {
|
||||||
itemMetaClass="item-color-section"
|
dcmrtClassNames += ' selected';
|
||||||
onItemClick={onClick}
|
}
|
||||||
>
|
|
||||||
<div>
|
if (isDisabled) {
|
||||||
<div className="item-label" style={{ marginBottom: 4 }}>
|
dcmrtClassNames += ' isDisabled';
|
||||||
{label}
|
}
|
||||||
|
|
||||||
|
const warningIcon = (
|
||||||
|
<span>
|
||||||
|
<Icon name="exclamation-triangle" />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableListItem = (
|
||||||
|
<TableListItem
|
||||||
|
key={index}
|
||||||
|
itemKey={index}
|
||||||
|
itemIndex={index}
|
||||||
|
itemClass={itemClass}
|
||||||
|
itemMeta={isDisabled ? warningIcon : <ColoredCircle color={color} />}
|
||||||
|
itemMetaClass="item-color-section"
|
||||||
|
onItemClick={() => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="item-label" style={{ marginBottom: 4 }}>
|
||||||
|
<a data-tip data-for={`StructureHover${index}`}>
|
||||||
|
<span>{label}</span>
|
||||||
|
</a>
|
||||||
|
<ReactTooltip
|
||||||
|
id={`StructureHover${index}`}
|
||||||
|
delayShow={250}
|
||||||
|
place="right"
|
||||||
|
border={true}
|
||||||
|
type="light"
|
||||||
|
>
|
||||||
|
<span>{label}</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
{!isDisabled && (
|
||||||
<Icon
|
<Icon
|
||||||
className={`eye-icon ${isVisible && '--visible'}`}
|
className={`eye-icon ${isVisible && '--visible'}`}
|
||||||
name={isVisible ? 'eye' : 'eye-closed'}
|
name={isVisible ? 'eye' : 'eye-closed'}
|
||||||
@@ -49,37 +89,72 @@ const StructureSetItem = ({
|
|||||||
height="20px"
|
height="20px"
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newVisibility = !isVisible;
|
const newVisibility = !isVisible;
|
||||||
setIsVisible(newVisibility);
|
setIsVisible(newVisibility);
|
||||||
onVisibilityChange(newVisibility);
|
onVisibilityChange(newVisibility);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{false && <div className="item-info">{'...'}</div>}
|
|
||||||
{false && (
|
|
||||||
<div className="item-actions">
|
|
||||||
<button
|
|
||||||
className="btnAction"
|
|
||||||
onClick={() => console.log('Relabelling...')}
|
|
||||||
>
|
|
||||||
<span style={{ marginRight: '4px' }}>
|
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
|
||||||
</span>
|
|
||||||
Relabel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btnAction"
|
|
||||||
onClick={() => console.log('Editing description...')}
|
|
||||||
>
|
|
||||||
<span style={{ marginRight: '4px' }}>
|
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
|
||||||
</span>
|
|
||||||
Description
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TableListItem>
|
|
||||||
|
{false && <div className="item-info">{'...'}</div>}
|
||||||
|
{false && (
|
||||||
|
<div className="item-actions">
|
||||||
|
<button
|
||||||
|
className="btnAction"
|
||||||
|
onClick={() => console.log('Relabelling...')}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: '4px' }}>
|
||||||
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
|
</span>
|
||||||
|
Relabel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btnAction"
|
||||||
|
onClick={() => console.log('Editing description...')}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: '4px' }}>
|
||||||
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
|
</span>
|
||||||
|
Description
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={dcmrtClassNames}>
|
||||||
|
<React.Fragment>
|
||||||
|
{isDisabled ? (
|
||||||
|
<OverlayTrigger
|
||||||
|
key={index}
|
||||||
|
placement="left"
|
||||||
|
overlay={
|
||||||
|
<Tooltip
|
||||||
|
placement="left"
|
||||||
|
className="in tooltip-warning"
|
||||||
|
id="tooltip-left"
|
||||||
|
>
|
||||||
|
<div className="warningTitle">Unsupported Region</div>
|
||||||
|
<div className="warningContent">
|
||||||
|
Contour type currently unsupported.
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>{tableListItem}</div>
|
||||||
|
</OverlayTrigger>
|
||||||
|
) : (
|
||||||
|
<React.Fragment>{tableListItem} </React.Fragment>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -94,7 +169,7 @@ StructureSetItem.propTypes = {
|
|||||||
|
|
||||||
StructureSetItem.defaultProps = {
|
StructureSetItem.defaultProps = {
|
||||||
itemClass: '',
|
itemClass: '',
|
||||||
onClick: () => { },
|
onClick: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StructureSetItem;
|
export default StructureSetItem;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export default function getSourceDisplaySet(studies, rtStructDisplaySet) {
|
||||||
|
const referencedDisplaySet = _getReferencedDisplaySet(
|
||||||
|
rtStructDisplaySet,
|
||||||
|
studies
|
||||||
|
);
|
||||||
|
|
||||||
|
rtStructDisplaySet.load(referencedDisplaySet, studies);
|
||||||
|
|
||||||
|
return referencedDisplaySet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getReferencedDisplaySet = (rtStructDisplaySet, studies) => {
|
||||||
|
let allDisplaySets = [];
|
||||||
|
|
||||||
|
studies.forEach(study => {
|
||||||
|
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherDisplaySets = allDisplaySets.filter(
|
||||||
|
ds => ds.displaySetInstanceUID !== rtStructDisplaySet.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const ReferencedSeriesSequence = Array.isArray(
|
||||||
|
rtStructDisplaySet.metadata.ReferencedSeriesSequence
|
||||||
|
)
|
||||||
|
? rtStructDisplaySet.metadata.ReferencedSeriesSequence
|
||||||
|
: [rtStructDisplaySet.metadata.ReferencedSeriesSequence];
|
||||||
|
|
||||||
|
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||||
|
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||||
|
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||||
|
);
|
||||||
|
|
||||||
|
return referencedDisplaySet;
|
||||||
|
};
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id,
|
id,
|
||||||
|
version,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -20,8 +21,10 @@ export default {
|
|||||||
preRegistration({ servicesManager, configuration = {} }) {
|
preRegistration({ servicesManager, configuration = {} }) {
|
||||||
init({ servicesManager, configuration });
|
init({ servicesManager, configuration });
|
||||||
},
|
},
|
||||||
getPanelModule({ commandsManager }) {
|
getPanelModule({ commandsManager, servicesManager, api }) {
|
||||||
const ExtendedRTPanel = props => {
|
const ExtendedRTPanel = props => {
|
||||||
|
const { activeContexts } = api.hooks.useAppContext();
|
||||||
|
|
||||||
const contourItemClickHandler = contourData => {
|
const contourItemClickHandler = contourData => {
|
||||||
commandsManager.runCommand('jumpToImage', contourData);
|
commandsManager.runCommand('jumpToImage', contourData);
|
||||||
};
|
};
|
||||||
@@ -30,6 +33,8 @@ export default {
|
|||||||
<RTPanel
|
<RTPanel
|
||||||
{...props}
|
{...props}
|
||||||
onContourItemClick={contourItemClickHandler}
|
onContourItemClick={contourItemClickHandler}
|
||||||
|
activeContexts={activeContexts}
|
||||||
|
contexts={api.contexts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -51,8 +56,10 @@ export default {
|
|||||||
if (study && study.series) {
|
if (study && study.series) {
|
||||||
for (let j = 0; j < study.series.length; j++) {
|
for (let j = 0; j < study.series.length; j++) {
|
||||||
const series = study.series[j];
|
const series = study.series[j];
|
||||||
console.log(studies);
|
if (
|
||||||
if (['RTSTRUCT', 'RTPLAN', 'RTDOSE'].includes(series.Modality)) {
|
/* Could be expanded to contain RTPLAN and RTDOSE information in the future */
|
||||||
|
['RTSTRUCT'].includes(series.Modality)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import * as dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
|
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
|
||||||
@@ -16,7 +16,6 @@ export default async function loadRTStruct(
|
|||||||
studies
|
studies
|
||||||
) {
|
) {
|
||||||
const rtStructModule = cornerstoneTools.getModule('rtstruct');
|
const rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||||
|
|
||||||
// Set here is loading is asynchronous.
|
// Set here is loading is asynchronous.
|
||||||
// If this function throws its set back to false.
|
// If this function throws its set back to false.
|
||||||
rtStructDisplaySet.isLoaded = true;
|
rtStructDisplaySet.isLoaded = true;
|
||||||
@@ -67,8 +66,9 @@ export default async function loadRTStruct(
|
|||||||
|
|
||||||
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
||||||
|
|
||||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
debugger;
|
||||||
|
|
||||||
|
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||||
const ROIContour = ROIContourSequence[i];
|
const ROIContour = ROIContourSequence[i];
|
||||||
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
||||||
|
|
||||||
@@ -76,26 +76,17 @@ export default async function loadRTStruct(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setROIContourMetadata(
|
const isSupported = false;
|
||||||
structureSet,
|
|
||||||
StructureSetROISequence,
|
|
||||||
RTROIObservationsSequence,
|
|
||||||
ROIContour
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let c = 0; c < ContourSequence.length; c++) {
|
const ContourSequenceArray = _toArray(ContourSequence);
|
||||||
|
|
||||||
|
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
||||||
const {
|
const {
|
||||||
ContourImageSequence,
|
ContourImageSequence,
|
||||||
ContourData,
|
ContourData,
|
||||||
NumberOfContourPoints,
|
NumberOfContourPoints,
|
||||||
ContourGeometricType,
|
ContourGeometricType,
|
||||||
} = ContourSequence[c];
|
} = ContourSequenceArray[c];
|
||||||
|
|
||||||
if (ContourGeometricType !== 'CLOSED_PLANAR') {
|
|
||||||
// TODO: Do we want to visualise types other than closed planar?
|
|
||||||
// We could easily do open planar.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
||||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||||
@@ -107,27 +98,47 @@ export default async function loadRTStruct(
|
|||||||
|
|
||||||
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
|
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
|
||||||
const points = [];
|
const points = [];
|
||||||
|
let measurementData;
|
||||||
|
|
||||||
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
|
switch (ContourGeometricType) {
|
||||||
points.push({
|
case 'CLOSED_PLANAR':
|
||||||
x: ContourData[p],
|
case 'OPEN_PLANAR':
|
||||||
y: ContourData[p + 1],
|
case 'POINT':
|
||||||
z: ContourData[p + 2],
|
isSupported = true;
|
||||||
});
|
|
||||||
|
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
|
||||||
|
points.push({
|
||||||
|
x: ContourData[p],
|
||||||
|
y: ContourData[p + 1],
|
||||||
|
z: ContourData[p + 2],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
transformPointsToImagePlane(points, imagePlane);
|
||||||
|
|
||||||
|
measurementData = {
|
||||||
|
handles: {
|
||||||
|
points,
|
||||||
|
},
|
||||||
|
type: ContourGeometricType,
|
||||||
|
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
|
||||||
|
ROINumber: ReferencedROINumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
imageIdSpecificToolData.push(measurementData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
transformPointsToImagePlane(points, imagePlane);
|
|
||||||
|
|
||||||
const measurementData = {
|
|
||||||
handles: {
|
|
||||||
points,
|
|
||||||
},
|
|
||||||
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
|
|
||||||
ROINumber: ReferencedROINumber,
|
|
||||||
};
|
|
||||||
|
|
||||||
imageIdSpecificToolData.push(measurementData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setROIContourMetadata(
|
||||||
|
structureSet,
|
||||||
|
StructureSetROISequence,
|
||||||
|
RTROIObservationsSequence,
|
||||||
|
ROIContour,
|
||||||
|
isSupported
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setToolEnabledIfNotEnabled(rtStructDisplayToolName);
|
_setToolEnabledIfNotEnabled(rtStructDisplayToolName);
|
||||||
@@ -157,7 +168,8 @@ function _setROIContourMetadata(
|
|||||||
structureSet,
|
structureSet,
|
||||||
StructureSetROISequence,
|
StructureSetROISequence,
|
||||||
RTROIObservationsSequence,
|
RTROIObservationsSequence,
|
||||||
ROIContour
|
ROIContour,
|
||||||
|
isSupported
|
||||||
) {
|
) {
|
||||||
const StructureSetROI = StructureSetROISequence.find(
|
const StructureSetROI = StructureSetROISequence.find(
|
||||||
structureSetROI =>
|
structureSetROI =>
|
||||||
@@ -169,6 +181,7 @@ function _setROIContourMetadata(
|
|||||||
ROIName: StructureSetROI.ROIName,
|
ROIName: StructureSetROI.ROIName,
|
||||||
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
|
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
|
||||||
ROIDescription: StructureSetROI.ROIDescription,
|
ROIDescription: StructureSetROI.ROIDescription,
|
||||||
|
isSupported,
|
||||||
visible: true,
|
visible: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,7 +251,7 @@ function _setROIContourRTROIObservations(
|
|||||||
|
|
||||||
function _setToolEnabledIfNotEnabled(toolName) {
|
function _setToolEnabledIfNotEnabled(toolName) {
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||||
const { element } = enabledElement;
|
const { element, image } = enabledElement;
|
||||||
const tool = cornerstoneTools.getToolForElement(element, toolName);
|
const tool = cornerstoneTools.getToolForElement(element, toolName);
|
||||||
|
|
||||||
if (tool.mode !== 'enabled') {
|
if (tool.mode !== 'enabled') {
|
||||||
@@ -246,7 +259,9 @@ function _setToolEnabledIfNotEnabled(toolName) {
|
|||||||
cornerstoneTools.setToolEnabled(toolName);
|
cornerstoneTools.setToolEnabled(toolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
cornerstone.updateImage(element);
|
if (image) {
|
||||||
|
cornerstone.updateImage(element);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,3 +322,7 @@ function _getImageIdSopInstanceUidPairsForDisplaySet(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _toArray(objOrArray) {
|
||||||
|
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
|
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
|
||||||
|
|
||||||
import TOOL_NAMES from '../utils/toolNames';
|
import TOOL_NAMES from '../utils/toolNames';
|
||||||
|
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||||
|
|
||||||
// Cornerstone 3rd party dev kit imports
|
// Cornerstone 3rd party dev kit imports
|
||||||
const draw = importInternal('drawing/draw');
|
const draw = importInternal('drawing/draw');
|
||||||
|
const drawCircle = importInternal('drawing/drawCircle');
|
||||||
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
|
const drawJoinedLines = importInternal('drawing/drawJoinedLines');
|
||||||
const getNewContext = importInternal('drawing/getNewContext');
|
const getNewContext = importInternal('drawing/getNewContext');
|
||||||
const BaseTool = importInternal('base/BaseTool');
|
const BaseTool = importInternal('base/BaseTool');
|
||||||
@@ -36,11 +37,17 @@ export default class RTStructDisplayTool extends BaseTool {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lineWidth, opacity } = rtstructModule.configuration;
|
const {
|
||||||
|
lineWidth,
|
||||||
|
opacity,
|
||||||
|
highlightOpacity,
|
||||||
|
} = rtstructModule.configuration;
|
||||||
|
|
||||||
// We have tool data for this element - iterate over each one and draw it
|
// We have tool data for this element - iterate over each one and draw it
|
||||||
const context = getNewContext(eventData.canvasContext.canvas);
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
|
||||||
|
let crossHairCenter;
|
||||||
|
|
||||||
for (let i = 0; i < toolState.data.length; i++) {
|
for (let i = 0; i < toolState.data.length; i++) {
|
||||||
const data = toolState.data[i];
|
const data = toolState.data[i];
|
||||||
|
|
||||||
@@ -72,22 +79,76 @@ export default class RTStructDisplayTool extends BaseTool {
|
|||||||
const colorArray = ROIContourData.colorArray;
|
const colorArray = ROIContourData.colorArray;
|
||||||
const color = `rgba(${colorArray[0]},${colorArray[1]},${
|
const color = `rgba(${colorArray[0]},${colorArray[1]},${
|
||||||
colorArray[2]
|
colorArray[2]
|
||||||
},${opacity})`;
|
},${opacity})`;
|
||||||
|
|
||||||
lineWidth;
|
let highlight = data.highlight;
|
||||||
|
const options = { color, lineWidth };
|
||||||
|
|
||||||
draw(context, context => {
|
if (highlight) {
|
||||||
drawJoinedLines(
|
crossHairCenter = { x: 0, y: 0 };
|
||||||
context,
|
|
||||||
eventData.element,
|
points.forEach(point => {
|
||||||
points[points.length - 1],
|
crossHairCenter.x += point.x;
|
||||||
points,
|
crossHairCenter.y += point.y;
|
||||||
{
|
});
|
||||||
color,
|
|
||||||
lineWidth,
|
crossHairCenter.x /= points.length;
|
||||||
}
|
crossHairCenter.y /= points.length;
|
||||||
);
|
|
||||||
|
// TODO: Disabling hightlight for now, it'd be good to bring it back
|
||||||
|
// when we have a good way of doing this for SEG.
|
||||||
|
|
||||||
|
// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
|
||||||
|
// colorArray[2]
|
||||||
|
// },${highlightOpacity})`;
|
||||||
|
|
||||||
|
// Draw highlight lines.
|
||||||
|
|
||||||
|
delete data.highlight; // Don't highlight on next render.
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case 'CLOSED_PLANAR':
|
||||||
|
this._renderClosedPlanar(context, eventData.element, points, options);
|
||||||
|
break;
|
||||||
|
case 'POINT':
|
||||||
|
this._renderPoint(context, eventData.element, points, options);
|
||||||
|
break;
|
||||||
|
case 'OPEN_PLANAR':
|
||||||
|
this._renderOpenPlanar(context, eventData.element, points, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crossHairCenter) {
|
||||||
|
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||||
|
color: toolColors.getActiveColor(),
|
||||||
|
lineWidth: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderClosedPlanar(context, element, points, options) {
|
||||||
|
draw(context, context => {
|
||||||
|
drawJoinedLines(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
points[points.length - 1],
|
||||||
|
points,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderPoint(context, element, points, options) {
|
||||||
|
draw(context, context => {
|
||||||
|
drawCircle(context, element, points[0], 3, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderOpenPlanar(context, element, points, options) {
|
||||||
|
draw(context, context => {
|
||||||
|
drawJoinedLines(context, element, points[0], points, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,13 +5,16 @@ import structureSetReferencesSeriesInstanceUid from './lib/structureSetReference
|
|||||||
// We should put this as a helper somewhere as we are using it in mutliple places.
|
// We should put this as a helper somewhere as we are using it in mutliple places.
|
||||||
function refreshViewport() {
|
function refreshViewport() {
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||||
cornerstone.updateImage(enabledElement.element);
|
if (enabledElement.image) {
|
||||||
|
cornerstone.updateImage(enabledElement.element);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
opacity: 1.0,
|
opacity: 0.75,
|
||||||
|
highlightOpacity: 0.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
@@ -81,7 +84,9 @@ function _setStructureSetVisible(SeriesInstanceUID, visible = true) {
|
|||||||
const StructureSet = getStructureSet(SeriesInstanceUID);
|
const StructureSet = getStructureSet(SeriesInstanceUID);
|
||||||
|
|
||||||
if (StructureSet) {
|
if (StructureSet) {
|
||||||
StructureSet.visible = visible;
|
StructureSet.ROIContours.forEach(ROIContour => {
|
||||||
|
ROIContour.visible = visible;
|
||||||
|
});
|
||||||
|
|
||||||
refreshViewport();
|
refreshViewport();
|
||||||
}
|
}
|
||||||
@@ -154,15 +159,10 @@ function setToggleROIContour(SeriesInstanceUID, ROINumber) {
|
|||||||
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
|
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
|
||||||
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
|
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
|
||||||
*/
|
*/
|
||||||
function getStructuresSetsWhichReferenceSeriesInstanceUid(
|
function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
|
||||||
SeriesInstanceUID
|
|
||||||
) {
|
|
||||||
const { StructureSets } = state;
|
const { StructureSets } = state;
|
||||||
return StructureSets.filter(StructureSet =>
|
return StructureSets.filter(StructureSet =>
|
||||||
structureSetReferencesSeriesInstanceUid(
|
structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
|
||||||
StructureSet,
|
|
||||||
SeriesInstanceUID
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
|
||||||
|
const { importInternal } = cornerstoneTools;
|
||||||
|
const draw = importInternal('drawing/draw');
|
||||||
|
const drawLine = importInternal('drawing/drawLine');
|
||||||
|
const getNewContext = importInternal('drawing/getNewContext');
|
||||||
|
|
||||||
|
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
const { element } = eventData;
|
||||||
|
|
||||||
|
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||||
|
|
||||||
|
const { clientWidth: width, clientHeight: height } = element;
|
||||||
|
|
||||||
|
const offset = 10;
|
||||||
|
|
||||||
|
draw(context, context => {
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||||
|
{ x: width, y: centerCanvas.y },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||||
|
{ x: 0, y: centerCanvas.y },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||||
|
{ x: centerCanvas.x, y: height },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||||
|
{ x: centerCanvas.x, y: 0 },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,6 +3,182 @@
|
|||||||
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.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.5...@ohif/extension-dicom-segmentation@0.4.6) (2020-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.4...@ohif/extension-dicom-segmentation@0.4.5) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.3...@ohif/extension-dicom-segmentation@0.4.4) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.2...@ohif/extension-dicom-segmentation@0.4.3) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.1...@ohif/extension-dicom-segmentation@0.4.2) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.0...@ohif/extension-dicom-segmentation@0.4.1) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.4...@ohif/extension-dicom-segmentation@0.4.0) (2020-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.3...@ohif/extension-dicom-segmentation@0.3.4) (2020-08-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.2...@ohif/extension-dicom-segmentation@0.3.3) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.1...@ohif/extension-dicom-segmentation@0.3.2) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.0...@ohif/extension-dicom-segmentation@0.3.1) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.6...@ohif/extension-dicom-segmentation@0.3.0) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.5...@ohif/extension-dicom-segmentation@0.2.6) (2020-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.4...@ohif/extension-dicom-segmentation@0.2.5) (2020-05-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.3...@ohif/extension-dicom-segmentation@0.2.4) (2020-05-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.2...@ohif/extension-dicom-segmentation@0.2.3) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.1...@ohif/extension-dicom-segmentation@0.2.2) (2020-04-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.2.0...@ohif/extension-dicom-segmentation@0.2.1) (2020-04-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Dropdown not updating when selecting segment ([#1675](https://github.com/OHIF/Viewers/issues/1675)) ([20e366f](https://github.com/OHIF/Viewers/commit/20e366ffbd76781f48dc99c9f59e24b255b5e5df)), closes [#1669](https://github.com/OHIF/Viewers/issues/1669)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.2...@ohif/extension-dicom-segmentation@0.2.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.1...@ohif/extension-dicom-segmentation@0.1.2) (2020-04-09)
|
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.1...@ohif/extension-dicom-segmentation@0.1.2) (2020-04-09)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-dicom-segmentation",
|
"name": "@ohif/extension-dicom-segmentation",
|
||||||
"version": "0.1.2",
|
"version": "0.4.6",
|
||||||
"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",
|
||||||
@@ -30,14 +30,16 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "4.12.5",
|
"cornerstone-tools": "^4.20.1",
|
||||||
"dcmjs": "^0.6.1",
|
"dcmjs": "0.16.3",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"react-select": "^3.0.8"
|
"gl-matrix": "^3.3.0",
|
||||||
|
"react-select": "^3.0.8",
|
||||||
|
"react-tooltip": "4.2.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { MODULE_TYPES, utils } from '@ohif/core';
|
|
||||||
import loadSegmentation from './loadSegmentation';
|
|
||||||
|
|
||||||
// TODO: Should probably use dcmjs for this
|
|
||||||
const SOP_CLASS_UIDS = {
|
|
||||||
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
|
||||||
|
|
||||||
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
|
||||||
// same SOP Class.
|
|
||||||
const OHIFDicomSegSopClassHandler = {
|
|
||||||
id: 'OHIFDicomSegSopClassHandler',
|
|
||||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
|
||||||
sopClassUIDs,
|
|
||||||
getDisplaySetFromSeries: function (
|
|
||||||
series,
|
|
||||||
study,
|
|
||||||
dicomWebClient,
|
|
||||||
authorizationHeaders
|
|
||||||
) {
|
|
||||||
const instance = series.getFirstInstance();
|
|
||||||
const metadata = instance.getData().metadata;
|
|
||||||
|
|
||||||
const {
|
|
||||||
SeriesDate,
|
|
||||||
SeriesTime,
|
|
||||||
SeriesDescription,
|
|
||||||
FrameOfReferenceUID,
|
|
||||||
SOPInstanceUID,
|
|
||||||
SeriesInstanceUID,
|
|
||||||
StudyInstanceUID,
|
|
||||||
} = metadata;
|
|
||||||
|
|
||||||
const segDisplaySet = {
|
|
||||||
Modality: 'SEG',
|
|
||||||
displaySetInstanceUID: utils.guid(),
|
|
||||||
wadoRoot: study.getData().wadoRoot,
|
|
||||||
wadoUri: instance.getData().wadouri,
|
|
||||||
SOPInstanceUID,
|
|
||||||
SeriesInstanceUID,
|
|
||||||
StudyInstanceUID,
|
|
||||||
FrameOfReferenceUID,
|
|
||||||
authorizationHeaders,
|
|
||||||
metadata,
|
|
||||||
isDerived: true,
|
|
||||||
referencedDisplaySetUID: null, // Assigned when loaded.
|
|
||||||
labelmapIndex: null, // Assigned when loaded.
|
|
||||||
isLoaded: false,
|
|
||||||
SeriesDate,
|
|
||||||
SeriesTime,
|
|
||||||
SeriesDescription,
|
|
||||||
};
|
|
||||||
|
|
||||||
segDisplaySet.load = function (referencedDisplaySet, studies) {
|
|
||||||
return loadSegmentation(
|
|
||||||
segDisplaySet,
|
|
||||||
referencedDisplaySet,
|
|
||||||
studies
|
|
||||||
).catch(error => {
|
|
||||||
segDisplaySet.isLoaded = false;
|
|
||||||
throw new Error(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return segDisplaySet;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OHIFDicomSegSopClassHandler;
|
|
||||||
@@ -12,14 +12,6 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dcmseg-segment-item .segment-label {
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: calc(100% - 50px);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dcmseg-segment-item .segment-info {
|
.dcmseg-segment-item .segment-info {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
@@ -62,3 +54,30 @@
|
|||||||
.dcmseg-segment-item .segment-actions .btnAction i {
|
.dcmseg-segment-item .segment-actions .btnAction i {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dcmseg-segment-item .segment-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segment-item .segment-label span {
|
||||||
|
overflow-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: calc(100% - 40px); /* calc(100% - 50px); 20px = eye icon */
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segment-item .segment-label .eye-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segment-item .segment-label .eye-icon:hover {
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segment-item .segment-label .eye-icon.--visible {
|
||||||
|
color: var(--default-color);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TableListItem, Icon } from '@ohif/ui';
|
import { TableListItem, Icon } from '@ohif/ui';
|
||||||
|
import ReactTooltip from 'react-tooltip';
|
||||||
|
|
||||||
import './SegmentItem.css';
|
import './SegmentItem.css';
|
||||||
|
|
||||||
@@ -17,48 +18,87 @@ ColoredCircle.propTypes = {
|
|||||||
color: PropTypes.array.isRequired,
|
color: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SegmentItem = ({ index, label, onClick, itemClass, color }) => (
|
const SegmentItem = ({
|
||||||
<div className="dcmseg-segment-item">
|
index,
|
||||||
<TableListItem
|
label,
|
||||||
key={index}
|
onClick,
|
||||||
itemKey={index}
|
itemClass,
|
||||||
itemIndex={index}
|
color,
|
||||||
itemClass={itemClass}
|
visible = true,
|
||||||
itemMeta={<ColoredCircle color={color} />}
|
onVisibilityChange,
|
||||||
itemMetaClass="segment-color-section"
|
}) => {
|
||||||
onItemClick={onClick}
|
const [isVisible, setIsVisible] = useState(visible);
|
||||||
>
|
|
||||||
<div>
|
useEffect(() => {
|
||||||
<div className="segment-label" style={{ marginBottom: 4 }}>
|
setIsVisible(visible);
|
||||||
{label}
|
}, [visible]);
|
||||||
</div>
|
|
||||||
{false && <div className="segment-info">{'...'}</div>}
|
return (
|
||||||
{false && (
|
<div className="dcmseg-segment-item">
|
||||||
<div className="segment-actions">
|
<TableListItem
|
||||||
<button
|
key={index}
|
||||||
className="btnAction"
|
itemKey={index}
|
||||||
onClick={() => console.log('Relabelling...')}
|
itemIndex={index}
|
||||||
|
itemClass={itemClass}
|
||||||
|
itemMeta={<ColoredCircle color={color} />}
|
||||||
|
itemMetaClass="segment-color-section"
|
||||||
|
onItemClick={onClick}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="segment-label" style={{ marginBottom: 4 }}>
|
||||||
|
<a data-tip data-for={`SegmentHover${index}`}>
|
||||||
|
<span>{label}</span>
|
||||||
|
</a>
|
||||||
|
<ReactTooltip
|
||||||
|
id={`SegmentHover${index}`}
|
||||||
|
delayShow={250}
|
||||||
|
place="right"
|
||||||
|
border={true}
|
||||||
|
type="light"
|
||||||
>
|
>
|
||||||
<span style={{ marginRight: '4px' }}>
|
<span>{label}</span>
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
</ReactTooltip>
|
||||||
</span>
|
<Icon
|
||||||
Relabel
|
className={`eye-icon ${isVisible && '--visible'}`}
|
||||||
</button>
|
name={isVisible ? 'eye' : 'eye-closed'}
|
||||||
<button
|
width="20px"
|
||||||
className="btnAction"
|
height="20px"
|
||||||
onClick={() => console.log('Editing description...')}
|
onClick={event => {
|
||||||
>
|
event.stopPropagation();
|
||||||
<span style={{ marginRight: '4px' }}>
|
const newVisibility = !isVisible;
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
setIsVisible(newVisibility);
|
||||||
</span>
|
onVisibilityChange(newVisibility);
|
||||||
Description
|
}}
|
||||||
</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{false && <div className="segment-info">{'...'}</div>}
|
||||||
</div>
|
{false && (
|
||||||
</TableListItem>
|
<div className="segment-actions">
|
||||||
</div>
|
<button
|
||||||
);
|
className="btnAction"
|
||||||
|
onClick={() => console.log('Relabelling...')}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: '4px' }}>
|
||||||
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
|
</span>
|
||||||
|
Relabel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btnAction"
|
||||||
|
onClick={() => console.log('Editing description...')}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: '4px' }}>
|
||||||
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
|
</span>
|
||||||
|
Description
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableListItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
SegmentItem.propTypes = {
|
SegmentItem.propTypes = {
|
||||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
@@ -70,7 +110,7 @@ SegmentItem.propTypes = {
|
|||||||
|
|
||||||
SegmentItem.defaultProps = {
|
SegmentItem.defaultProps = {
|
||||||
itemClass: '',
|
itemClass: '',
|
||||||
onClick: () => { }
|
onClick: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SegmentItem;
|
export default SegmentItem;
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dcmseg-segmentation-panel.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dcmseg-segmentation-panel h3 {
|
.dcmseg-segmentation-panel h3 {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|||||||
+378
-181
@@ -1,10 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { utils, log } from '@ohif/core';
|
import { utils, log } from '@ohif/core';
|
||||||
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
||||||
|
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
|
||||||
|
|
||||||
|
import setActiveLabelmap from '../../utils/setActiveLabelMap';
|
||||||
|
import refreshViewports from '../../utils/refreshViewports';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BrushColorSelector,
|
BrushColorSelector,
|
||||||
@@ -19,37 +23,59 @@ import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
|
|||||||
|
|
||||||
const { studyMetadataManager } = utils;
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
const refreshViewport = () => {
|
|
||||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
|
||||||
cornerstone.updateImage(enabledElement.element);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SegmentationPanel component
|
* SegmentationPanel component
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Array} props.studies - Studies data
|
||||||
* @param {Array} props.studies
|
* @param {Array} props.viewports - Viewports data (viewportSpecificData)
|
||||||
* @param {Array} props.viewports - viewportSpecificData
|
* @param {number} props.activeIndex - Active viewport index
|
||||||
* @param {number} props.activeIndex - activeViewportIndex
|
* @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded
|
||||||
|
* @param {Function} props.onSegmentItemClick - Segment click handler
|
||||||
|
* @param {Function} props.onSegmentVisibilityChange - Segment visibiliy change handler
|
||||||
|
* @param {Function} props.onConfigurationChange - Configuration change handler
|
||||||
|
* @param {Function} props.activeContexts - List of active application contexts
|
||||||
|
* @param {Function} props.contexts - List of available application contexts
|
||||||
* @returns component
|
* @returns component
|
||||||
*/
|
*/
|
||||||
const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
const SegmentationPanel = ({
|
||||||
|
studies,
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
isOpen,
|
||||||
|
onSegmentItemClick,
|
||||||
|
onSegmentVisibilityChange,
|
||||||
|
onConfigurationChange,
|
||||||
|
onDisplaySetLoadFailure,
|
||||||
|
onSelectedSegmentationChange,
|
||||||
|
activeContexts = [],
|
||||||
|
contexts = {},
|
||||||
|
}) => {
|
||||||
|
const isVTK = () => activeContexts.includes(contexts.VTK);
|
||||||
|
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: wrap get/set interactions with the cornerstoneTools
|
* TODO: wrap get/set interactions with the cornerstoneTools
|
||||||
* store with context to make these kind of things less blurry.
|
* store with context to make these kind of things less blurry.
|
||||||
*/
|
*/
|
||||||
const { configuration } = cornerstoneTools.getModule('segmentation');
|
const { configuration } = cornerstoneTools.getModule('segmentation');
|
||||||
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
|
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: We shouldn't hardcode brushColor color, in the future
|
||||||
|
* the SEG may set the colorLUT to whatever it wants.
|
||||||
|
*/
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
brushRadius: DEFAULT_BRUSH_RADIUS,
|
brushRadius: DEFAULT_BRUSH_RADIUS,
|
||||||
brushColor: 'rgba(221, 85, 85, 1)', /* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
|
brushColor: 'rgba(221, 85, 85, 1)',
|
||||||
selectedSegment: null,
|
selectedSegment: null,
|
||||||
selectedSegmentation: null,
|
selectedSegmentation: null,
|
||||||
showSegSettings: false,
|
showSegmentationSettings: false,
|
||||||
brushStackState: null,
|
brushStackState: null,
|
||||||
labelmapList: [],
|
labelmapList: [],
|
||||||
segmentList: []
|
segmentList: [],
|
||||||
|
cachedSegmentsProperties: [],
|
||||||
|
isLoading: false,
|
||||||
|
isDisabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -57,10 +83,37 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
log.warn('Segmentation Panel: labelmap modified', event);
|
log.warn('Segmentation Panel: labelmap modified', event);
|
||||||
const module = cornerstoneTools.getModule('segmentation');
|
const module = cornerstoneTools.getModule('segmentation');
|
||||||
const activeViewport = viewports[activeIndex];
|
const activeViewport = viewports[activeIndex];
|
||||||
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
activeViewport.StudyInstanceUID
|
||||||
|
);
|
||||||
|
const firstImageId = studyMetadata.getFirstImageId(
|
||||||
|
activeViewport.displaySetInstanceUID
|
||||||
|
);
|
||||||
updateState('brushStackState', module.state.series[firstImageId]);
|
updateState('brushStackState', module.state.series[firstImageId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
||||||
|
*
|
||||||
|
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
||||||
|
* or other components that could rely on loaded segmentations that
|
||||||
|
* the segments were loaded so that e.g. when the user opens the panel
|
||||||
|
* before the segments are fully loaded, the panel can subscribe to this custom event
|
||||||
|
* and update itself with the new segments.
|
||||||
|
*
|
||||||
|
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
||||||
|
* updated after the segments are loaded) that React its not aware of its changes
|
||||||
|
* because the module object its not passed in to the panel component as prop but accessed externally.
|
||||||
|
*
|
||||||
|
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
||||||
|
* allows us to easily watch the module or the segmentations loading process in any other component
|
||||||
|
* without subscribing to external events.
|
||||||
|
*/
|
||||||
|
document.addEventListener(
|
||||||
|
'extensiondicomsegmentationsegloaded',
|
||||||
|
refreshSegmentations
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These are specific to each element;
|
* These are specific to each element;
|
||||||
* Need to iterate cornerstone-tools tracked enabled elements?
|
* Need to iterate cornerstone-tools tracked enabled elements?
|
||||||
@@ -74,6 +127,10 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
document.removeEventListener(
|
||||||
|
'extensiondicomsegmentationsegloaded',
|
||||||
|
refreshSegmentations
|
||||||
|
);
|
||||||
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
||||||
enabledElement.removeEventListener(
|
enabledElement.removeEventListener(
|
||||||
'cornerstonetoolslabelmapmodified',
|
'cornerstonetoolslabelmapmodified',
|
||||||
@@ -81,154 +138,324 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
});
|
}, [activeIndex, viewports]);
|
||||||
|
|
||||||
useEffect(() => {
|
const refreshSegmentations = useCallback(() => {
|
||||||
const module = cornerstoneTools.getModule('segmentation');
|
const module = cornerstoneTools.getModule('segmentation');
|
||||||
const activeViewport = viewports[activeIndex];
|
const activeViewport = viewports[activeIndex];
|
||||||
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
|
|
||||||
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
|
|
||||||
const brushStackState = module.state.series[firstImageId];
|
|
||||||
|
|
||||||
if (brushStackState) {
|
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
|
||||||
const labelmap3D = brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
if (!isDisabled) {
|
||||||
const labelmapList = getLabelmapList(brushStackState, firstImageId, activeViewport);
|
const studyMetadata = studyMetadataManager.get(
|
||||||
const segmentList = getSegmentList(labelmap3D, firstImageId);
|
activeViewport.StudyInstanceUID
|
||||||
setState(state => ({
|
);
|
||||||
...state,
|
const firstImageId = studyMetadata.getFirstImageId(
|
||||||
brushStackState,
|
activeViewport.displaySetInstanceUID
|
||||||
selectedSegmentation: brushStackState.activeLabelmapIndex,
|
);
|
||||||
labelmapList,
|
const brushStackState = module.state.series[firstImageId];
|
||||||
segmentList
|
if (brushStackState) {
|
||||||
}));
|
const labelmap3D =
|
||||||
} else {
|
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||||
setState(state => ({
|
const labelmapList = getLabelmapList(
|
||||||
...state,
|
brushStackState,
|
||||||
labelmapList: [],
|
firstImageId,
|
||||||
segmentList: [],
|
activeViewport
|
||||||
}));
|
);
|
||||||
|
const segmentList = getSegmentList(
|
||||||
|
labelmap3D,
|
||||||
|
firstImageId,
|
||||||
|
brushStackState
|
||||||
|
);
|
||||||
|
setState(state => ({
|
||||||
|
...state,
|
||||||
|
brushStackState,
|
||||||
|
selectedSegmentation: brushStackState.activeLabelmapIndex,
|
||||||
|
labelmapList,
|
||||||
|
segmentList,
|
||||||
|
isDisabled,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setState(state => ({
|
||||||
|
...state,
|
||||||
|
labelmapList: [],
|
||||||
|
segmentList: [],
|
||||||
|
isDisabled,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [studies, viewports, activeIndex]);
|
}, [viewports, activeIndex, state.isLoading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshSegmentations();
|
||||||
|
}, [
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
isOpen,
|
||||||
|
state.selectedSegmentation,
|
||||||
|
activeContexts,
|
||||||
|
state.isLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
/* Handle open/closed panel behaviour */
|
/* Handle open/closed panel behaviour */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateState('showSegSettings', state.showSegSettings && !isOpen);
|
setState(state => ({
|
||||||
|
...state,
|
||||||
|
showSegmentationSettings: state.showSegmentationSettings && !isOpen,
|
||||||
|
}));
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const getLabelmapList = (brushStackState, firstImageId, activeViewport) => {
|
const getLabelmapList = useCallback(
|
||||||
/* Get list of SEG labelmaps specific to active viewport (reference series) */
|
(brushStackState, firstImageId, activeViewport) => {
|
||||||
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
|
/* Get list of SEG labelmaps specific to active viewport (reference series) */
|
||||||
activeViewport.StudyInstanceUID,
|
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
|
||||||
activeViewport.SeriesInstanceUID
|
activeViewport.StudyInstanceUID,
|
||||||
);
|
activeViewport.SeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
return referencedSegDisplaysets.map((displaySet, index) => {
|
return referencedSegDisplaysets.map((displaySet, index) => {
|
||||||
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
|
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
|
||||||
|
|
||||||
/* Map to display representation */
|
/* Map to display representation */
|
||||||
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
||||||
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
||||||
const isActiveLabelmap =
|
const isActiveLabelmap =
|
||||||
labelmapIndex === brushStackState.activeLabelmapIndex;
|
labelmapIndex === brushStackState.activeLabelmapIndex;
|
||||||
const displayDate = date.format('ddd, MMM Do YYYY');
|
const displayDate = date.format('ddd, MMM Do YYYY');
|
||||||
const displayTime = date.format('h:mm:ss a');
|
const displayTime = date.format('h:mm:ss a');
|
||||||
const displayDescription = displaySet.SeriesDescription;
|
const displayDescription = displaySet.SeriesDescription;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: labelmapIndex,
|
value: labelmapIndex,
|
||||||
title: displayDescription,
|
title: displayDescription,
|
||||||
description: displayDate,
|
description: displayDate,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const activatedLabelmapIndex = await _setActiveLabelmap(
|
const activatedLabelmapIndex = await setActiveLabelmap(
|
||||||
activeViewport,
|
activeViewport,
|
||||||
studies,
|
studies,
|
||||||
displaySet,
|
displaySet,
|
||||||
firstImageId,
|
() => onSelectedSegmentationChange(),
|
||||||
brushStackState.activeLabelmapIndex
|
onDisplaySetLoadFailure
|
||||||
);
|
);
|
||||||
updateState('selectedSegmentation', activatedLabelmapIndex);
|
updateState('selectedSegmentation', activatedLabelmapIndex);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
[studies]
|
||||||
|
);
|
||||||
|
|
||||||
const getSegmentList = (labelmap3D, firstImageId) => {
|
const getSegmentList = useCallback(
|
||||||
/*
|
(labelmap3D, firstImageId, brushStackState) => {
|
||||||
* Newly created segments have no `meta`
|
/*
|
||||||
* So we instead build a list of all segment indexes in use
|
* Newly created segments have no `meta`
|
||||||
* Then find any associated metadata
|
* So we instead build a list of all segment indexes in use
|
||||||
*/
|
* Then find any associated metadata
|
||||||
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
|
*/
|
||||||
.reduce((acc, labelmap2D) => {
|
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
|
||||||
if (labelmap2D) {
|
.reduce((acc, labelmap2D) => {
|
||||||
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
|
if (labelmap2D) {
|
||||||
|
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
|
||||||
|
|
||||||
for (let i = 0; i < segmentIndexes.length; i++) {
|
for (let i = 0; i < segmentIndexes.length; i++) {
|
||||||
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
|
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
|
||||||
acc.push(segmentIndexes[i]);
|
acc.push(segmentIndexes[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const module = cornerstoneTools.getModule('segmentation');
|
||||||
|
const colorLutTable =
|
||||||
|
module.state.colorLutTables[labelmap3D.colorLUTIndex];
|
||||||
|
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
|
||||||
|
|
||||||
|
const segmentList = [];
|
||||||
|
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
|
||||||
|
const segmentIndex = uniqueSegmentIndexes[i];
|
||||||
|
|
||||||
|
const color = colorLutTable[segmentIndex];
|
||||||
|
let segmentLabel = '(unlabeled)';
|
||||||
|
let segmentNumber = segmentIndex;
|
||||||
|
|
||||||
|
/* Meta */
|
||||||
|
if (hasLabelmapMeta) {
|
||||||
|
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
|
||||||
|
|
||||||
|
if (segmentMeta) {
|
||||||
|
segmentNumber = segmentMeta.SegmentNumber;
|
||||||
|
segmentLabel = segmentMeta.SegmentLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
const sameSegment = state.selectedSegment === segmentNumber;
|
||||||
}, [])
|
const setCurrentSelectedSegment = () => {
|
||||||
.sort((a, b) => a - b);
|
_setActiveSegment(
|
||||||
|
firstImageId,
|
||||||
|
segmentNumber,
|
||||||
|
labelmap3D.activeSegmentIndex
|
||||||
|
);
|
||||||
|
updateState('selectedSegment', sameSegment ? null : segmentNumber);
|
||||||
|
|
||||||
const module = cornerstoneTools.getModule('segmentation');
|
const validIndexList = [];
|
||||||
const colorLutTable =
|
labelmap3D.labelmaps2D.forEach((labelMap2D, index) => {
|
||||||
module.state.colorLutTables[labelmap3D.colorLUTIndex];
|
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
|
||||||
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
|
validIndexList.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const avg = array => array.reduce((a, b) => a + b) / array.length;
|
||||||
|
const average = avg(validIndexList);
|
||||||
|
const closest = validIndexList.reduce((prev, curr) => {
|
||||||
|
return Math.abs(curr - average) < Math.abs(prev - average)
|
||||||
|
? curr
|
||||||
|
: prev;
|
||||||
|
});
|
||||||
|
|
||||||
const segmentList = [];
|
if (isCornerstone()) {
|
||||||
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
|
const enabledElements = cornerstone.getEnabledElements();
|
||||||
const segmentIndex = uniqueSegmentIndexes[i];
|
const element = enabledElements[activeIndex].element;
|
||||||
|
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||||
|
|
||||||
const color = colorLutTable[segmentIndex];
|
if (!toolState) {
|
||||||
let segmentLabel = '(unlabeled)';
|
return;
|
||||||
let segmentNumber = segmentIndex;
|
}
|
||||||
|
|
||||||
/* Meta */
|
const imageIds = toolState.data[0].imageIds;
|
||||||
if (hasLabelmapMeta) {
|
const imageId = imageIds[closest];
|
||||||
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
|
const frameIndex = imageIds.indexOf(imageId);
|
||||||
|
|
||||||
if (segmentMeta) {
|
const SOPInstanceUID = cornerstone.metaData.get(
|
||||||
segmentNumber = segmentMeta.SegmentNumber;
|
'SOPInstanceUID',
|
||||||
segmentLabel = segmentMeta.SegmentLabel;
|
imageId
|
||||||
|
);
|
||||||
|
const StudyInstanceUID = cornerstone.metaData.get(
|
||||||
|
'StudyInstanceUID',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
|
||||||
|
DICOMSegTempCrosshairsTool.addCrosshair(
|
||||||
|
element,
|
||||||
|
imageId,
|
||||||
|
segmentNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
onSegmentItemClick({
|
||||||
|
StudyInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameIndex,
|
||||||
|
activeViewportIndex: activeIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVTK()) {
|
||||||
|
const activeViewport = viewports[activeIndex];
|
||||||
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
activeViewport.StudyInstanceUID
|
||||||
|
);
|
||||||
|
const allDisplaySets = studyMetadata.getDisplaySets();
|
||||||
|
const currentDisplaySet = allDisplaySets.find(
|
||||||
|
displaySet =>
|
||||||
|
displaySet.displaySetInstanceUID ===
|
||||||
|
activeViewport.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const frame = labelmap3D.labelmaps2D[closest];
|
||||||
|
|
||||||
|
onSegmentItemClick({
|
||||||
|
studies,
|
||||||
|
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
|
||||||
|
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
|
||||||
|
SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
|
||||||
|
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
|
||||||
|
segmentNumber,
|
||||||
|
frameIndex: closest,
|
||||||
|
frame,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSegmentVisible = () => {
|
||||||
|
return !labelmap3D.segmentsHidden[segmentIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSegmentVisibility = () => {
|
||||||
|
const segmentsHidden = labelmap3D.segmentsHidden;
|
||||||
|
segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
|
||||||
|
return !segmentsHidden[segmentIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
const cachedSegmentProperties =
|
||||||
|
state.cachedSegmentsProperties[segmentNumber];
|
||||||
|
let visible = isSegmentVisible();
|
||||||
|
if (
|
||||||
|
cachedSegmentProperties &&
|
||||||
|
cachedSegmentProperties.visible !== visible
|
||||||
|
) {
|
||||||
|
toggleSegmentVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
segmentList.push(
|
||||||
|
<SegmentItem
|
||||||
|
key={segmentNumber}
|
||||||
|
itemClass={`segment-item ${sameSegment && 'selected'}`}
|
||||||
|
onClick={() => setCurrentSelectedSegment()}
|
||||||
|
label={segmentLabel}
|
||||||
|
index={segmentNumber}
|
||||||
|
color={color}
|
||||||
|
visible={visible}
|
||||||
|
onVisibilityChange={newVisibility => {
|
||||||
|
if (isCornerstone()) {
|
||||||
|
const enabledElements = cornerstone.getEnabledElements();
|
||||||
|
const element = enabledElements[activeIndex].element;
|
||||||
|
module.setters.toggleSegmentVisibility(
|
||||||
|
element,
|
||||||
|
segmentNumber,
|
||||||
|
brushStackState.activeLabelmapIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVTK()) {
|
||||||
|
onSegmentVisibilityChange(segmentNumber, newVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCachedSegmentsProperties(segmentNumber, {
|
||||||
|
visible: newVisibility,
|
||||||
|
});
|
||||||
|
refreshViewports();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sameSegment = state.selectedSegment === segmentNumber;
|
return segmentList;
|
||||||
const setCurrentSelectedSegment = () => {
|
|
||||||
_setActiveSegment(
|
|
||||||
firstImageId,
|
|
||||||
segmentNumber,
|
|
||||||
labelmap3D.activeSegmentIndex
|
|
||||||
);
|
|
||||||
updateState('selectedSegment', sameSegment ? null : segmentNumber);
|
|
||||||
};
|
|
||||||
|
|
||||||
segmentList.push(
|
/*
|
||||||
<SegmentItem
|
* Let's iterate over segmentIndexes ^ above
|
||||||
key={segmentNumber}
|
* If meta has a match, use it to show info
|
||||||
itemClass={`segment-item ${sameSegment && 'selected'}`}
|
* If now, add "no-meta" class
|
||||||
onClick={setCurrentSelectedSegment}
|
* Show default name
|
||||||
label={segmentLabel}
|
*/
|
||||||
index={segmentNumber}
|
},
|
||||||
color={color}
|
[activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return segmentList;
|
const updateCachedSegmentsProperties = (segmentNumber, properties) => {
|
||||||
|
const segmentsProperties = state.cachedSegmentsProperties;
|
||||||
|
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
|
||||||
|
|
||||||
/*
|
segmentsProperties[segmentNumber] = segmentProperties
|
||||||
* Let's iterate over segmentIndexes ^ above
|
? { ...segmentProperties, ...properties }
|
||||||
* If meta has a match, use it to show info
|
: properties;
|
||||||
* If now, add "no-meta" class
|
|
||||||
* Show default name
|
updateState('cachedSegmentsProperties', segmentsProperties);
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateState('cachedSegmentsProperties', []);
|
||||||
|
}, [activeContexts]);
|
||||||
|
|
||||||
const updateState = (field, value) => {
|
const updateState = (field, value) => {
|
||||||
setState(state => ({ ...state, [field]: value }));
|
setState(state => ({ ...state, [field]: value }));
|
||||||
};
|
};
|
||||||
@@ -270,15 +497,13 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const module = cornerstoneTools.getModule('segmentation');
|
const module = cornerstoneTools.getModule('segmentation');
|
||||||
const colorLutTable =
|
const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
|
||||||
module.state.colorLutTables[labelmap3D.colorLUTIndex];
|
|
||||||
const color = colorLutTable[labelmap3D.activeSegmentIndex];
|
const color = colorLutTable[labelmap3D.activeSegmentIndex];
|
||||||
|
|
||||||
return `rgba(${color.join(',')})`;
|
return `rgba(${color.join(',')})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateConfiguration = newConfiguration => {
|
const updateConfiguration = newConfiguration => {
|
||||||
/* Supported configuration */
|
|
||||||
configuration.renderFill = newConfiguration.renderFill;
|
configuration.renderFill = newConfiguration.renderFill;
|
||||||
configuration.renderOutline = newConfiguration.renderOutline;
|
configuration.renderOutline = newConfiguration.renderOutline;
|
||||||
configuration.shouldRenderInactiveLabelmaps =
|
configuration.shouldRenderInactiveLabelmaps =
|
||||||
@@ -288,26 +513,35 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
configuration.outlineWidth = newConfiguration.outlineWidth;
|
configuration.outlineWidth = newConfiguration.outlineWidth;
|
||||||
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
|
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
|
||||||
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
|
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
|
||||||
refreshViewport();
|
onConfigurationChange(newConfiguration);
|
||||||
|
refreshViewports();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (state.showSegSettings) {
|
const disabledConfigurationFields = [
|
||||||
|
'outlineAlpha',
|
||||||
|
'shouldRenderInactiveLabelmaps',
|
||||||
|
];
|
||||||
|
if (state.showSegmentationSettings) {
|
||||||
return (
|
return (
|
||||||
<SegmentationSettings
|
<SegmentationSettings
|
||||||
|
disabledFields={isVTK() ? disabledConfigurationFields : []}
|
||||||
configuration={configuration}
|
configuration={configuration}
|
||||||
onBack={() => updateState('showSegSettings', false)}
|
onBack={() => updateState('showSegmentationSettings', false)}
|
||||||
onChange={updateConfiguration}
|
onChange={updateConfiguration}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="dcmseg-segmentation-panel">
|
<div
|
||||||
|
className={`dcmseg-segmentation-panel ${state.isDisabled &&
|
||||||
|
'disabled'}`}
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className="cog-icon"
|
className="cog-icon"
|
||||||
name="cog"
|
name="cog"
|
||||||
width="25px"
|
width="25px"
|
||||||
height="25px"
|
height="25px"
|
||||||
onClick={() => updateState('showSegSettings', true)}
|
onClick={() => updateState('showSegmentationSettings', true)}
|
||||||
/>
|
/>
|
||||||
{false && (
|
{false && (
|
||||||
<form className="selector-form">
|
<form className="selector-form">
|
||||||
@@ -328,9 +562,9 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
|||||||
<h3>Segmentations</h3>
|
<h3>Segmentations</h3>
|
||||||
<div className="segmentations">
|
<div className="segmentations">
|
||||||
<SegmentationSelect
|
<SegmentationSelect
|
||||||
value={
|
value={state.labelmapList.find(
|
||||||
state.labelmapList.find(i => i.value === state.selectedSegmentation) || null
|
i => i.value === state.selectedSegmentation
|
||||||
}
|
)}
|
||||||
formatOptionLabel={SegmentationItem}
|
formatOptionLabel={SegmentationItem}
|
||||||
options={state.labelmapList}
|
options={state.labelmapList}
|
||||||
/>
|
/>
|
||||||
@@ -400,43 +634,6 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
|
|||||||
return referencedDisplaysets;
|
return referencedDisplaysets;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {*} viewportSpecificData
|
|
||||||
* @param {*} studies
|
|
||||||
* @param {*} displaySet
|
|
||||||
* @param {*} firstImageId
|
|
||||||
* @param {*} activeLabelmapIndex
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const _setActiveLabelmap = async (
|
|
||||||
viewportSpecificData,
|
|
||||||
studies,
|
|
||||||
displaySet,
|
|
||||||
firstImageId,
|
|
||||||
activeLabelmapIndex
|
|
||||||
) => {
|
|
||||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
|
||||||
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
|
||||||
return displaySet.labelmapIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!displaySet.isLoaded) {
|
|
||||||
// What props does this expect `viewportSpecificData` to have?
|
|
||||||
// TODO: Should this return the `labelmapIndex`?
|
|
||||||
await displaySet.load(viewportSpecificData, studies);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { state } = cornerstoneTools.getModule('segmentation');
|
|
||||||
const brushStackState = state.series[firstImageId];
|
|
||||||
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
|
|
||||||
|
|
||||||
refreshViewport();
|
|
||||||
|
|
||||||
return displaySet.labelmapIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} firstImageId
|
* @param {*} firstImageId
|
||||||
@@ -456,7 +653,7 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
|
|||||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||||
labelmap3D.activeSegmentIndex = segmentIndex;
|
labelmap3D.activeSegmentIndex = segmentIndex;
|
||||||
|
|
||||||
refreshViewport();
|
refreshViewports();
|
||||||
|
|
||||||
return segmentIndex;
|
return segmentIndex;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const defaultColor = computedstyle.getPropertyValue('--default-color');
|
|||||||
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
||||||
|
|
||||||
const segmentationSelectStyles = {
|
const segmentationSelectStyles = {
|
||||||
|
singleValue: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
width: '100%'
|
||||||
|
}),
|
||||||
control: (base, state) => ({
|
control: (base, state) => ({
|
||||||
...base,
|
...base,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|||||||
+26
-22
@@ -4,7 +4,7 @@ import { Range } from '@ohif/ui';
|
|||||||
|
|
||||||
import './SegmentationSettings.css';
|
import './SegmentationSettings.css';
|
||||||
|
|
||||||
const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
renderFill: configuration.renderFill,
|
renderFill: configuration.renderFill,
|
||||||
renderOutline: configuration.renderOutline,
|
renderOutline: configuration.renderOutline,
|
||||||
@@ -70,28 +70,32 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
|||||||
/>
|
/>
|
||||||
{state.renderOutline && (
|
{state.renderOutline && (
|
||||||
<>
|
<>
|
||||||
<CustomRange
|
{!disabledFields.includes('outlineAlpha') && (
|
||||||
value={state.outlineAlpha * 100}
|
<CustomRange
|
||||||
label="Opacity"
|
value={state.outlineAlpha * 100}
|
||||||
showPercentage
|
label="Opacity"
|
||||||
step={1}
|
showPercentage
|
||||||
min={0}
|
step={1}
|
||||||
max={100}
|
min={0}
|
||||||
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
max={100}
|
||||||
/>
|
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
||||||
<CustomRange
|
/>
|
||||||
value={state.outlineWidth}
|
)}
|
||||||
label="Width"
|
{!disabledFields.includes('outlineWidth') && (
|
||||||
showValue
|
<CustomRange
|
||||||
step={1}
|
value={state.outlineWidth}
|
||||||
min={0}
|
label="Width"
|
||||||
max={5}
|
showValue
|
||||||
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
step={1}
|
||||||
/>
|
min={0}
|
||||||
|
max={5}
|
||||||
|
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(state.renderFill || state.renderOutline) && (
|
{(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
|
||||||
<div
|
<div
|
||||||
className="settings-group"
|
className="settings-group"
|
||||||
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
|
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
|
||||||
@@ -103,7 +107,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
|||||||
/>
|
/>
|
||||||
{state.shouldRenderInactiveLabelmaps && (
|
{state.shouldRenderInactiveLabelmaps && (
|
||||||
<>
|
<>
|
||||||
{state.renderFill && (
|
{state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
|
||||||
<CustomRange
|
<CustomRange
|
||||||
label="Fill Opacity"
|
label="Fill Opacity"
|
||||||
showPercentage
|
showPercentage
|
||||||
@@ -114,7 +118,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
|||||||
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
|
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{state.renderOutline && (
|
{state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
|
||||||
<CustomRange
|
<CustomRange
|
||||||
label="Outline Opacity"
|
label="Outline Opacity"
|
||||||
showPercentage
|
showPercentage
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { MODULE_TYPES, utils } from '@ohif/core';
|
||||||
|
import loadSegmentation from './loadSegmentation';
|
||||||
|
import getSourceDisplaySet from './getSourceDisplaySet';
|
||||||
|
|
||||||
|
// TODO: Should probably use dcmjs for this
|
||||||
|
const SOP_CLASS_UIDS = {
|
||||||
|
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||||
|
|
||||||
|
export default function getSopClassHandlerModule({ servicesManager }) {
|
||||||
|
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||||
|
// same SOP Class.
|
||||||
|
return {
|
||||||
|
id: 'OHIFDicomSegSopClassHandler',
|
||||||
|
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||||
|
sopClassUIDs,
|
||||||
|
getDisplaySetFromSeries: function(
|
||||||
|
series,
|
||||||
|
study,
|
||||||
|
dicomWebClient,
|
||||||
|
authorizationHeaders
|
||||||
|
) {
|
||||||
|
const instance = series.getFirstInstance();
|
||||||
|
const metadata = instance.getData().metadata;
|
||||||
|
|
||||||
|
const {
|
||||||
|
SeriesDate,
|
||||||
|
SeriesTime,
|
||||||
|
SeriesDescription,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesNumber,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
|
const segDisplaySet = {
|
||||||
|
Modality: 'SEG',
|
||||||
|
displaySetInstanceUID: utils.guid(),
|
||||||
|
wadoRoot: study.getData().wadoRoot,
|
||||||
|
wadoUri: instance.getData().wadouri,
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
StudyInstanceUID,
|
||||||
|
FrameOfReferenceUID,
|
||||||
|
authorizationHeaders,
|
||||||
|
metadata,
|
||||||
|
isDerived: true,
|
||||||
|
referencedDisplaySetUID: null, // Assigned when loaded.
|
||||||
|
labelmapIndex: null, // Assigned when loaded.
|
||||||
|
isLoaded: false,
|
||||||
|
SeriesDate,
|
||||||
|
SeriesTime,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDescription,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
segDisplaySet.getSourceDisplaySet = function(studies) {
|
||||||
|
return getSourceDisplaySet(studies, segDisplaySet);
|
||||||
|
};
|
||||||
|
|
||||||
|
segDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||||
|
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
|
||||||
|
};
|
||||||
|
|
||||||
|
return segDisplaySet;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import setActiveLabelmap from './utils/setActiveLabelMap';
|
||||||
|
import { classes } from '@ohif/core';
|
||||||
|
|
||||||
|
const { ImageSet } = classes;
|
||||||
|
|
||||||
|
export default function getSourceDisplaySet(studies, segDisplaySet) {
|
||||||
|
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
|
||||||
|
|
||||||
|
setActiveLabelmap(referencedDisplaySet, studies, segDisplaySet);
|
||||||
|
|
||||||
|
return referencedDisplaySet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||||
|
let allDisplaySets = [];
|
||||||
|
|
||||||
|
studies.forEach(study => {
|
||||||
|
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||||
|
});
|
||||||
|
|
||||||
|
const otherDisplaySets = allDisplaySets.filter(
|
||||||
|
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const { metadata } = segDisplaySet;
|
||||||
|
|
||||||
|
let referencedSeriesInstanceUIDs;
|
||||||
|
|
||||||
|
if (metadata.ReferencedSeriesSequence) {
|
||||||
|
const ReferencedSeriesSequence = _toArray(
|
||||||
|
metadata.ReferencedSeriesSequence
|
||||||
|
);
|
||||||
|
|
||||||
|
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||||
|
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const { PerFrameFunctionalGroupsSequence } = metadata;
|
||||||
|
|
||||||
|
let SourceImageSequence;
|
||||||
|
|
||||||
|
if (metadata.SourceImageSequence) {
|
||||||
|
SourceImageSequence = metadata.SourceImageSequence;
|
||||||
|
} else {
|
||||||
|
const firstFunctionalGroups = _toArray(
|
||||||
|
PerFrameFunctionalGroupsSequence
|
||||||
|
)[0];
|
||||||
|
const { DerivationImageSequence } = firstFunctionalGroups;
|
||||||
|
|
||||||
|
SourceImageSequence = DerivationImageSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstSourceImage = _toArray(SourceImageSequence)[0];
|
||||||
|
|
||||||
|
const { ReferencedSOPInstanceUID } = firstSourceImage;
|
||||||
|
|
||||||
|
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
|
||||||
|
otherDisplaySets,
|
||||||
|
ReferencedSOPInstanceUID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||||
|
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||||
|
);
|
||||||
|
|
||||||
|
return referencedDisplaySet;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
|
||||||
|
displaySets,
|
||||||
|
SOPInstanceUID
|
||||||
|
) => {
|
||||||
|
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
|
||||||
|
|
||||||
|
for (let i = 0; i < imageSets.length; i++) {
|
||||||
|
const { images } = imageSets[i];
|
||||||
|
for (let j = 0; j < images.length; j++) {
|
||||||
|
if (images[j].SOPInstanceUID === SOPInstanceUID) {
|
||||||
|
return [images[j].getData().metadata.SeriesInstanceUID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _toArray(arrayOrObject) {
|
||||||
|
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import panelModule from './panelModule.js';
|
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||||
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js';
|
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'com.ohif.dicom-segmentation',
|
id: 'com.ohif.dicom-segmentation',
|
||||||
|
version,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -21,10 +24,97 @@ export default {
|
|||||||
getToolbarModule({ servicesManager }) {
|
getToolbarModule({ servicesManager }) {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
},
|
},
|
||||||
getPanelModule({ servicesManager }) {
|
getPanelModule({ commandsManager, api, servicesManager }) {
|
||||||
return panelModule;
|
const { UINotificationService } = servicesManager.services;
|
||||||
},
|
|
||||||
getSopClassHandlerModule({ servicesManager }) {
|
const ExtendedSegmentationPanel = props => {
|
||||||
return sopClassHandlerModule;
|
const { activeContexts } = api.hooks.useAppContext();
|
||||||
|
|
||||||
|
const onDisplaySetLoadFailureHandler = error => {
|
||||||
|
UINotificationService.show({
|
||||||
|
title: 'DICOM Segmentation Loader',
|
||||||
|
message: error.message,
|
||||||
|
type: 'error',
|
||||||
|
autoClose: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const segmentItemClickHandler = data => {
|
||||||
|
commandsManager.runCommand('jumpToImage', data);
|
||||||
|
commandsManager.runCommand('jumpToSlice', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
|
||||||
|
commandsManager.runCommand('setSegmentConfiguration', {
|
||||||
|
segmentNumber,
|
||||||
|
visible,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConfigurationChangeHandler = configuration => {
|
||||||
|
commandsManager.runCommand('setSegmentationConfiguration', {
|
||||||
|
globalOpacity: configuration.fillAlpha,
|
||||||
|
outlineThickness: configuration.outlineWidth,
|
||||||
|
renderOutline: configuration.renderOutline,
|
||||||
|
visible: configuration.renderFill,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectedSegmentationChangeHandler = () => {
|
||||||
|
commandsManager.runCommand('requestNewSegmentation');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SegmentationPanel
|
||||||
|
{...props}
|
||||||
|
activeContexts={activeContexts}
|
||||||
|
contexts={api.contexts}
|
||||||
|
onSegmentItemClick={segmentItemClickHandler}
|
||||||
|
onSegmentVisibilityChange={onSegmentVisibilityChangeHandler}
|
||||||
|
onConfigurationChange={onConfigurationChangeHandler}
|
||||||
|
onSelectedSegmentationChange={onSelectedSegmentationChangeHandler}
|
||||||
|
onDisplaySetLoadFailure={onDisplaySetLoadFailureHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
menuOptions: [
|
||||||
|
{
|
||||||
|
icon: 'list',
|
||||||
|
label: 'Segmentations',
|
||||||
|
target: 'segmentation-panel',
|
||||||
|
isDisabled: studies => {
|
||||||
|
if (!studies) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < studies.length; i++) {
|
||||||
|
const study = studies[i];
|
||||||
|
|
||||||
|
if (study && study.series) {
|
||||||
|
for (let j = 0; j < study.series.length; j++) {
|
||||||
|
const series = study.series[j];
|
||||||
|
|
||||||
|
if (series.Modality === 'SEG') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
id: 'segmentation-panel',
|
||||||
|
component: ExtendedSegmentationPanel,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultContext: ['VIEWER'],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
getSopClassHandlerModule,
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import csTools from 'cornerstone-tools';
|
import csTools from 'cornerstone-tools';
|
||||||
|
import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -17,4 +18,6 @@ export default function init({ servicesManager, configuration = {} }) {
|
|||||||
alwaysEraseOnClick: true,
|
alwaysEraseOnClick: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
csTools.addTool(DICOMSegTempCrosshairsTool);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
import * as dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
|
||||||
@@ -34,28 +34,59 @@ export default async function loadSegmentation(
|
|||||||
referencedDisplaySet.SeriesInstanceUID
|
referencedDisplaySet.SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const results = _parseSeg(segArrayBuffer, imageIds);
|
return new Promise((resolve, reject) => {
|
||||||
|
let results;
|
||||||
|
|
||||||
if (!results) {
|
try {
|
||||||
throw new Error('Fractional segmentations are not yet supported');
|
results = _parseSeg(segArrayBuffer, imageIds);
|
||||||
}
|
} catch (error) {
|
||||||
|
segDisplaySet.isLoaded = false;
|
||||||
|
segDisplaySet.loadError = true;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
||||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||||
|
|
||||||
// TODO: Could define a color LUT based on colors in the SEG.
|
// TODO: Could define a color LUT based on colors in the SEG.
|
||||||
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
||||||
|
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
|
||||||
|
|
||||||
setters.labelmap3DByFirstImageId(
|
setters.labelmap3DByFirstImageId(
|
||||||
imageIds[0],
|
imageIds[0],
|
||||||
labelmapBuffer,
|
labelmapBuffer,
|
||||||
labelmapIndex,
|
labelmapIndex,
|
||||||
segMetadata,
|
segMetadata,
|
||||||
imageIds.length,
|
imageIds.length,
|
||||||
segmentsOnFrame
|
segmentsOnFrame,
|
||||||
);
|
colorLUTIndex
|
||||||
|
);
|
||||||
|
|
||||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
||||||
|
*
|
||||||
|
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
||||||
|
* or other components that could rely on loaded segmentations that
|
||||||
|
* the segments were loaded so that e.g. when the user opens the panel
|
||||||
|
* before the segments are fully loaded, the panel can subscribe to this custom event
|
||||||
|
* and update itself with the new segments.
|
||||||
|
*
|
||||||
|
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
||||||
|
* updated after the segments are loaded) that React its not aware of its changes
|
||||||
|
* because the module object its not passed in to the panel component as prop but accessed externally.
|
||||||
|
*
|
||||||
|
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
||||||
|
* allows us to easily watch the module or the segmentations loading process in any other component
|
||||||
|
* without subscribing to external events.
|
||||||
|
*/
|
||||||
|
console.log('Segmentation loaded.');
|
||||||
|
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
resolve(labelmapIndex);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getNextLabelmapIndex(firstImageId) {
|
function _getNextLabelmapIndex(firstImageId) {
|
||||||
@@ -79,6 +110,69 @@ function _getNextLabelmapIndex(firstImageId) {
|
|||||||
return labelmapIndex;
|
return labelmapIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _makeColorLUTAndGetIndex(segMetadata) {
|
||||||
|
const { setters, state } = cornerstoneTools.getModule('segmentation');
|
||||||
|
const { colorLutTables } = state;
|
||||||
|
const colorLUTIndex = _getNextColorLUTIndex();
|
||||||
|
|
||||||
|
const { data } = segMetadata;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!data.some(
|
||||||
|
segment =>
|
||||||
|
segment &&
|
||||||
|
(segment.ROIDisplayColor || segment.RecommendedDisplayCIELabValue)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Use default cornerstoneTools colorLUT.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorLUT = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const segment = data[i];
|
||||||
|
if (!segment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ROIDisplayColor, RecommendedDisplayCIELabValue } = segment;
|
||||||
|
|
||||||
|
if (RecommendedDisplayCIELabValue) {
|
||||||
|
const rgb = dcmjs.data.Colors.dicomlab2RGB(
|
||||||
|
RecommendedDisplayCIELabValue
|
||||||
|
).map(x => Math.round(x * 255));
|
||||||
|
|
||||||
|
colorLUT[i] = [...rgb, 255];
|
||||||
|
} else if (ROIDisplayColor) {
|
||||||
|
colorLUT[i] = [...ROIDisplayColor, 255];
|
||||||
|
} else {
|
||||||
|
colorLUT[i] = [...colorLutTables[0][i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
colorLUT.shift();
|
||||||
|
setters.colorLUT(colorLUTIndex, colorLUT);
|
||||||
|
|
||||||
|
return colorLUTIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getNextColorLUTIndex() {
|
||||||
|
const { state } = cornerstoneTools.getModule('segmentation');
|
||||||
|
const { colorLutTables } = state;
|
||||||
|
|
||||||
|
let colorLUTIndex = colorLutTables.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < colorLutTables.length; i++) {
|
||||||
|
if (!colorLutTables[i]) {
|
||||||
|
colorLUTIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorLUTIndex;
|
||||||
|
}
|
||||||
|
|
||||||
function _parseSeg(arrayBuffer, imageIds) {
|
function _parseSeg(arrayBuffer, imageIds) {
|
||||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||||
imageIds,
|
imageIds,
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
menuOptions: [
|
|
||||||
{
|
|
||||||
icon: 'list',
|
|
||||||
label: 'Segmentations',
|
|
||||||
target: 'segmentation-panel',
|
|
||||||
isDisabled: studies => {
|
|
||||||
if (!studies) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < studies.length; i++) {
|
|
||||||
const study = studies[i];
|
|
||||||
|
|
||||||
if (study && study.series) {
|
|
||||||
for (let j = 0; j < study.series.length; j++) {
|
|
||||||
const series = study.series[j];
|
|
||||||
|
|
||||||
if (series.Modality === 'SEG') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
id: 'segmentation-panel',
|
|
||||||
component: SegmentationPanel,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
defaultContext: ['VIEWER'],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import cornerstoneTools, {
|
||||||
|
importInternal,
|
||||||
|
getToolState,
|
||||||
|
toolColors,
|
||||||
|
getModule,
|
||||||
|
globalImageIdSpecificToolStateManager,
|
||||||
|
} from 'cornerstone-tools';
|
||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||||
|
import TOOL_NAMES from './TOOL_NAMES';
|
||||||
|
|
||||||
|
const { DICOM_SEG_TEMP_CROSSHAIRS_TOOL } = TOOL_NAMES;
|
||||||
|
const { getters } = getModule('segmentation');
|
||||||
|
|
||||||
|
// Cornerstone 3rd party dev kit imports
|
||||||
|
const BaseTool = importInternal('base/BaseTool');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
|
||||||
|
* @extends cornerstoneTools.BaseTool
|
||||||
|
*/
|
||||||
|
export default class DICOMSegTempCrosshairsTool extends BaseTool {
|
||||||
|
constructor(props = {}) {
|
||||||
|
const defaultProps = {
|
||||||
|
mixins: ['enabledOrDisabledBinaryTool'],
|
||||||
|
name: DICOM_SEG_TEMP_CROSSHAIRS_TOOL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialProps = Object.assign(defaultProps, props);
|
||||||
|
|
||||||
|
super(initialProps);
|
||||||
|
|
||||||
|
this._rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolData(evt) {
|
||||||
|
const eventData = evt.detail;
|
||||||
|
const { element } = eventData;
|
||||||
|
const toolState = getToolState(evt.currentTarget, this.name);
|
||||||
|
|
||||||
|
if (!toolState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have tool data for this element - iterate over each one and draw it
|
||||||
|
|
||||||
|
for (let i = 0; i < toolState.data.length; i++) {
|
||||||
|
const data = toolState.data[i];
|
||||||
|
const crossHairCenter = data.center;
|
||||||
|
|
||||||
|
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||||
|
color: toolColors.getActiveColor(),
|
||||||
|
lineWidth: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the crosshairs, we only render them for one redraw.
|
||||||
|
toolState.data.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DICOMSegTempCrosshairsTool.addCrosshair = (element, imageId, segmentNumber) => {
|
||||||
|
const labelmap3D = getters.labelmap3D(element);
|
||||||
|
const stackToolState = cornerstoneTools.getToolState(element, 'stack');
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
|
||||||
|
const { rows, columns } = enabledElement.image;
|
||||||
|
|
||||||
|
if (!stackToolState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIds = stackToolState.data[0].imageIds;
|
||||||
|
const imageIdIndex = imageIds.findIndex(imgId => imgId === imageId);
|
||||||
|
|
||||||
|
const labelmap2D = labelmap3D.labelmaps2D[imageIdIndex];
|
||||||
|
const { pixelData } = labelmap2D;
|
||||||
|
|
||||||
|
let xCenter = 0;
|
||||||
|
let yCenter = 0;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < rows; y++) {
|
||||||
|
for (let x = 0; x < columns; x++) {
|
||||||
|
if (pixelData[y * columns + x] === segmentNumber) {
|
||||||
|
count++;
|
||||||
|
xCenter += x + 0.5;
|
||||||
|
yCenter += y + 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xCenter /= count;
|
||||||
|
yCenter /= count;
|
||||||
|
|
||||||
|
const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||||
|
|
||||||
|
if (!globalToolState[imageId]) {
|
||||||
|
globalToolState[imageId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageIdSpecificToolState = globalToolState[imageId];
|
||||||
|
|
||||||
|
if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL]) {
|
||||||
|
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL] = { data: [] };
|
||||||
|
} else if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data) {
|
||||||
|
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolSpecificData =
|
||||||
|
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data;
|
||||||
|
|
||||||
|
toolSpecificData.push({ center: { x: xCenter, y: yCenter }, segmentNumber });
|
||||||
|
|
||||||
|
// Enable the tool if not enabled for the element.
|
||||||
|
|
||||||
|
const tool = cornerstoneTools.getToolForElement(
|
||||||
|
element,
|
||||||
|
DICOM_SEG_TEMP_CROSSHAIRS_TOOL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tool.mode !== 'enabled') {
|
||||||
|
// If not already active or passive, set passive so contours render.
|
||||||
|
cornerstoneTools.setToolEnabled(DICOM_SEG_TEMP_CROSSHAIRS_TOOL);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const TOOL_NAMES = {
|
||||||
|
DICOM_SEG_TEMP_CROSSHAIRS_TOOL: 'DICOMSegTempCrosshairsTool',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TOOL_NAMES;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import cornerstone from 'cornerstone-core';
|
||||||
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
|
||||||
|
const { importInternal } = cornerstoneTools;
|
||||||
|
const draw = importInternal('drawing/draw');
|
||||||
|
const drawLine = importInternal('drawing/drawLine');
|
||||||
|
const getNewContext = importInternal('drawing/getNewContext');
|
||||||
|
|
||||||
|
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||||
|
const context = getNewContext(eventData.canvasContext.canvas);
|
||||||
|
const { element } = eventData;
|
||||||
|
|
||||||
|
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||||
|
|
||||||
|
const { clientWidth: width, clientHeight: height } = element;
|
||||||
|
|
||||||
|
const offset = 10;
|
||||||
|
|
||||||
|
draw(context, context => {
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||||
|
{ x: width, y: centerCanvas.y },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||||
|
{ x: 0, y: centerCanvas.y },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||||
|
{ x: centerCanvas.x, y: height },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
context,
|
||||||
|
element,
|
||||||
|
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||||
|
{ x: centerCanvas.x, y: 0 },
|
||||||
|
options,
|
||||||
|
'canvas'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export default function refreshViewports() {
|
||||||
|
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||||
|
cornerstone.updateImage(enabledElement.element);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { utils, log } from '@ohif/core';
|
||||||
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
|
import refreshViewports from './refreshViewports';
|
||||||
|
|
||||||
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} viewportSpecificData
|
||||||
|
* @param {*} studies
|
||||||
|
* @param {*} displaySet
|
||||||
|
* @param {*} firstImageId
|
||||||
|
* @param {*} activeLabelmapIndex
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default async function setActiveLabelmap(
|
||||||
|
referencedDisplaySet,
|
||||||
|
studies,
|
||||||
|
displaySet,
|
||||||
|
callback = () => {},
|
||||||
|
onDisplaySetLoadFailure = err => {
|
||||||
|
throw new Error(err.message);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
referencedDisplaySet.StudyInstanceUID
|
||||||
|
);
|
||||||
|
const firstImageId = studyMetadata.getFirstImageId(
|
||||||
|
referencedDisplaySet.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
let { state } = cornerstoneTools.getModule('segmentation');
|
||||||
|
|
||||||
|
let brushStackState = state.series[firstImageId];
|
||||||
|
const activeLabelmapIndex = brushStackState
|
||||||
|
? brushStackState.activeLabelmapIndex
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||||
|
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
||||||
|
return displaySet.labelmapIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displaySet.isLoaded) {
|
||||||
|
const loadPromise = displaySet.load(referencedDisplaySet, studies);
|
||||||
|
|
||||||
|
loadPromise.catch(error => {
|
||||||
|
onDisplaySetLoadFailure(error);
|
||||||
|
|
||||||
|
// Return old index.
|
||||||
|
return activeLabelmapIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This might have just been created, so need to use the non-cached value.
|
||||||
|
state = cornerstoneTools.getModule('segmentation').state;
|
||||||
|
brushStackState = state.series[firstImageId];
|
||||||
|
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
|
||||||
|
|
||||||
|
refreshViewports();
|
||||||
|
callback();
|
||||||
|
|
||||||
|
return displaySet.labelmapIndex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
const merge = require('webpack-merge');
|
||||||
|
const path = require('path');
|
||||||
|
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||||
|
const pkg = require('./../package.json');
|
||||||
|
|
||||||
|
const ROOT_DIR = path.join(__dirname, './..');
|
||||||
|
const SRC_DIR = path.join(__dirname, '../src');
|
||||||
|
const DIST_DIR = path.join(__dirname, '../dist');
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||||
|
|
||||||
|
return merge(commonConfig, {
|
||||||
|
devtool: 'source-map',
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
hash: true,
|
||||||
|
timings: true,
|
||||||
|
assets: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
modules: false,
|
||||||
|
children: false,
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
sideEffects: true,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: ROOT_DIR,
|
||||||
|
library: 'OHIFExtDicomP10Downloader',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
libraryExport: 'default',
|
||||||
|
filename: pkg.main,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.2...@ohif/extension-dicom-tag-browser@0.0.3) (2020-09-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.0.2 (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Open Health Imaging Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# @ohif/extension-dicom-tag-browser
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@ohif/extension-dicom-tag-browser",
|
||||||
|
"version": "0.0.3",
|
||||||
|
"description": "OHIF extension for checking DICOM headers.",
|
||||||
|
"author": "OHIF",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "OHIF/Viewers",
|
||||||
|
"main": "dist/index.umd.js",
|
||||||
|
"module": "src/index.js",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.16.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||||
|
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||||
|
"build:package": "yarn run build",
|
||||||
|
"prepublishOnly": "yarn run build",
|
||||||
|
"start": "yarn run dev"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ohif/core": "^2.6.0",
|
||||||
|
"dcmjs": "0.16.0",
|
||||||
|
"react": "^16.8.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dicomweb-client": "^0.6.0",
|
||||||
|
"moment": "2.24.0",
|
||||||
|
"react-select": "^3.0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
|
const DicomBrowserSelect = ({ value, formatOptionLabel, options }) => (
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
formatOptionLabel={formatOptionLabel}
|
||||||
|
styles={dicomBrowserSelectStyles}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const computedstyle = getComputedStyle(document.body);
|
||||||
|
const uiGrayDarker = computedstyle.getPropertyValue('--ui-gray-darker');
|
||||||
|
const activeColor = computedstyle.getPropertyValue('--active-color');
|
||||||
|
const defaultColor = computedstyle.getPropertyValue('--default-color');
|
||||||
|
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
||||||
|
|
||||||
|
const dicomBrowserSelectStyles = {
|
||||||
|
singleValue: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
width: '100%',
|
||||||
|
}),
|
||||||
|
control: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: uiGrayDarker,
|
||||||
|
borderRadius: state.isFocused ? '5px 5px 5px 5px' : 5,
|
||||||
|
borderColor: state.isFocused ? activeColor : defaultColor,
|
||||||
|
minHeight: '50px',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: activeColor,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
menu: base => ({
|
||||||
|
...base,
|
||||||
|
borderRadius: 5,
|
||||||
|
background: uiGrayDarker,
|
||||||
|
}),
|
||||||
|
option: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
width: '100%',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:first-of-type': {
|
||||||
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
|
},
|
||||||
|
'&:last-of-type': {
|
||||||
|
borderBottomLeftRadius: 5,
|
||||||
|
borderBottomRightRadius: 5,
|
||||||
|
},
|
||||||
|
background: state.isSelected ? uiGrayDark : uiGrayDarker,
|
||||||
|
'&:hover': {
|
||||||
|
background: uiGrayDark,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DicomBrowserSelect;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.dcmseg-segmentation-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segmentation-item .segmentation-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segmentation-item .segmentation-meta-title {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: white;
|
||||||
|
max-width: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dcmseg-segmentation-item .segmentation-meta-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary-color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './DicomBrowserSelectItem.css';
|
||||||
|
|
||||||
|
const DicomBrowserSelectItem = ({ onClick, title, description }) => {
|
||||||
|
return (
|
||||||
|
<li className="dcmseg-segmentation-item" onClick={onClick}>
|
||||||
|
<div className="segmentation-meta">
|
||||||
|
<div className="segmentation-meta-title">{title}</div>
|
||||||
|
<div className="segmentation-meta-description">{description}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DicomBrowserSelectItem.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
DicomBrowserSelectItem.defaultProps = {
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DicomBrowserSelectItem;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.dicom-tag-browser-table {
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-table tr {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
color: var(--table-text-primary-color);
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-table th {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dicom-tag-browser-table th.dicom-tag-browser-table-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { classes } from '@ohif/core';
|
||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
import DicomBrowserSelect from './DicomBrowserSelect';
|
||||||
|
import moment from 'moment';
|
||||||
|
import './DicomTagBrowser.css';
|
||||||
|
import DicomBrowserSelectItem from './DicomBrowserSelectItem';
|
||||||
|
|
||||||
|
const { ImageSet } = classes;
|
||||||
|
const { DicomMetaDictionary } = dcmjs.data;
|
||||||
|
const { nameMap } = DicomMetaDictionary;
|
||||||
|
|
||||||
|
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||||
|
const [
|
||||||
|
activeDisplaySetInstanceUID,
|
||||||
|
setActiveDisplaySetInstanceUID,
|
||||||
|
] = useState(displaySetInstanceUID);
|
||||||
|
const [activeInstance, setActiveInstance] = useState(0);
|
||||||
|
const [tags, setTags] = useState([]);
|
||||||
|
const [instanceList, setInstanceList] = useState([]);
|
||||||
|
const [displaySetList, setDisplaySetList] = useState([]);
|
||||||
|
const [isImageStack, setIsImageStack] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const activeDisplaySet = displaySets.find(
|
||||||
|
ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const newDisplaySetList = displaySets.map(displaySet => {
|
||||||
|
const {
|
||||||
|
displaySetInstanceUID,
|
||||||
|
SeriesDate,
|
||||||
|
SeriesTime,
|
||||||
|
SeriesNumber,
|
||||||
|
SeriesDescription,
|
||||||
|
Modality,
|
||||||
|
} = displaySet;
|
||||||
|
|
||||||
|
/* Map to display representation */
|
||||||
|
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
||||||
|
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
||||||
|
const displayDate = date.format('ddd, MMM Do YYYY');
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: displaySetInstanceUID,
|
||||||
|
title: `${SeriesNumber} (${Modality}): ${SeriesDescription}`,
|
||||||
|
description: displayDate,
|
||||||
|
onClick: () => {
|
||||||
|
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
|
||||||
|
setActiveInstance(0);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let metadata;
|
||||||
|
const isImageStack = activeDisplaySet instanceof ImageSet;
|
||||||
|
|
||||||
|
let selectedInstanceValue;
|
||||||
|
let instanceList;
|
||||||
|
|
||||||
|
if (isImageStack) {
|
||||||
|
const { images } = activeDisplaySet;
|
||||||
|
const image = images[activeInstance];
|
||||||
|
|
||||||
|
instanceList = images.map((image, index) => {
|
||||||
|
const metadata = image.getData().metadata;
|
||||||
|
|
||||||
|
const { InstanceNumber } = metadata;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: index,
|
||||||
|
title: `Instance Number: ${InstanceNumber}`,
|
||||||
|
description: '',
|
||||||
|
onClick: () => {
|
||||||
|
setActiveInstance(index);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
metadata = image.getData().metadata;
|
||||||
|
} else {
|
||||||
|
metadata = activeDisplaySet.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTags(getSortedTags(metadata));
|
||||||
|
setInstanceList(instanceList);
|
||||||
|
setDisplaySetList(newDisplaySetList);
|
||||||
|
setIsImageStack(isImageStack);
|
||||||
|
}, [activeDisplaySetInstanceUID, activeInstance]);
|
||||||
|
|
||||||
|
const selectedDisplaySetValue = displaySetList.find(
|
||||||
|
ds => ds.value === activeDisplaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
let instanceSelectList = null;
|
||||||
|
|
||||||
|
if (isImageStack) {
|
||||||
|
const selectedInstanceValue = instanceList[activeInstance];
|
||||||
|
|
||||||
|
instanceSelectList = (
|
||||||
|
<DicomBrowserSelect
|
||||||
|
value={selectedInstanceValue}
|
||||||
|
formatOptionLabel={DicomBrowserSelectItem}
|
||||||
|
options={instanceList}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DicomBrowserSelect
|
||||||
|
value={selectedDisplaySetValue}
|
||||||
|
formatOptionLabel={DicomBrowserSelectItem}
|
||||||
|
options={displaySetList}
|
||||||
|
/>
|
||||||
|
{instanceSelectList}
|
||||||
|
<DicomTagTable tags={tags}></DicomTagTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function DicomTagTable({ tags }) {
|
||||||
|
const rows = getFormattedRowsFromTags(tags);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="dicom-tag-browser-table">
|
||||||
|
<tr>
|
||||||
|
<th className="dicom-tag-browser-table-left">Tag</th>
|
||||||
|
<th className="dicom-tag-browser-table-left">Value Representation</th>
|
||||||
|
<th className="dicom-tag-browser-table-left">Keyword</th>
|
||||||
|
<th className="dicom-tag-browser-table-left">Value</th>
|
||||||
|
</tr>
|
||||||
|
{rows.map(row => {
|
||||||
|
const className = row.className ? row.className : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={className}>
|
||||||
|
<td>{row[0]}</td>
|
||||||
|
<td className="dicom-tag-browser-table-center">{row[1]}</td>
|
||||||
|
<td>{row[2]}</td>
|
||||||
|
<td>{row[3]}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormattedRowsFromTags(tags) {
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
tags.forEach(tagInfo => {
|
||||||
|
if (tagInfo.vr === 'SQ') {
|
||||||
|
rows.push([
|
||||||
|
`${tagInfo.tagIndent}${tagInfo.tag}`,
|
||||||
|
tagInfo.vr,
|
||||||
|
tagInfo.keyword,
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { values } = tagInfo;
|
||||||
|
|
||||||
|
values.forEach((item, index) => {
|
||||||
|
const formatedRowsFromTags = getFormattedRowsFromTags(item);
|
||||||
|
|
||||||
|
rows.push([
|
||||||
|
`${item[0].tagIndent}(FFFE,E000)`,
|
||||||
|
'',
|
||||||
|
`Item #${index}`,
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
|
||||||
|
rows.push(...formatedRowsFromTags);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rows.push([
|
||||||
|
`${tagInfo.tagIndent}${tagInfo.tag}`,
|
||||||
|
tagInfo.vr,
|
||||||
|
tagInfo.keyword,
|
||||||
|
tagInfo.value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortedTags(metadata) {
|
||||||
|
const tagList = getRows(metadata);
|
||||||
|
|
||||||
|
// Sort top level tags, sequence groups are sorted when created.
|
||||||
|
_sortTagList(tagList);
|
||||||
|
|
||||||
|
return tagList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRows(metadata, depth = 0) {
|
||||||
|
// Tag, Type, Value, Keyword
|
||||||
|
|
||||||
|
const keywords = Object.keys(metadata);
|
||||||
|
|
||||||
|
let tagIndent = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < depth; i++) {
|
||||||
|
tagIndent += '>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth > 0) {
|
||||||
|
tagIndent += ' '; // If indented, add a space after the indents.
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < keywords.length; i++) {
|
||||||
|
let keyword = keywords[i];
|
||||||
|
|
||||||
|
if (keyword === '_vrMap') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagInfo = nameMap[keyword];
|
||||||
|
|
||||||
|
let value = metadata[keyword];
|
||||||
|
|
||||||
|
if (tagInfo && tagInfo.vr === 'SQ') {
|
||||||
|
const sequenceAsArray = toArray(value);
|
||||||
|
|
||||||
|
// Push line defining the sequence
|
||||||
|
|
||||||
|
const sequence = {
|
||||||
|
tag: tagInfo.tag,
|
||||||
|
tagIndent,
|
||||||
|
vr: tagInfo.vr,
|
||||||
|
keyword,
|
||||||
|
values: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
rows.push(sequence);
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
// Type 2 Sequence
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequenceAsArray.forEach(item => {
|
||||||
|
const sequenceRows = getRows(item, depth + 1);
|
||||||
|
|
||||||
|
// Sort the sequence group.
|
||||||
|
_sortTagList(sequenceRows);
|
||||||
|
|
||||||
|
sequence.values.push(sequenceRows);
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value.join('\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
value = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
if (value === null) {
|
||||||
|
value = ' ';
|
||||||
|
} else {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (value.InlineBinary) {
|
||||||
|
value = 'Inline Binary';
|
||||||
|
} else if (value.BulkDataURI) {
|
||||||
|
value = `Bulk Data URI`; //: ${value.BulkDataURI}`;
|
||||||
|
} else if (value.Alphabetic) {
|
||||||
|
value = value.Alphabetic;
|
||||||
|
} else {
|
||||||
|
console.error('Unrecognised Value for element:');
|
||||||
|
console.error(value);
|
||||||
|
value = ' ';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Unrecognised Value for element:');
|
||||||
|
console.error(value);
|
||||||
|
value = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag / vr/ keyword/ value
|
||||||
|
|
||||||
|
// Remove retired tags
|
||||||
|
keyword = keyword.replace('RETIRED_', '');
|
||||||
|
|
||||||
|
if (tagInfo) {
|
||||||
|
rows.push({
|
||||||
|
tag: tagInfo.tag,
|
||||||
|
tagIndent,
|
||||||
|
vr: tagInfo.vr,
|
||||||
|
keyword,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Private tag
|
||||||
|
const tag = `(${keyword.substring(0, 4)},${keyword.substring(4, 8)})`;
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
tag,
|
||||||
|
tagIndent,
|
||||||
|
vr: '',
|
||||||
|
keyword: 'Private Tag',
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(objectOrArray) {
|
||||||
|
return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sortTagList(tagList) {
|
||||||
|
tagList.sort((a, b) => {
|
||||||
|
if (a.tag < b.tag) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DicomTagBrowser;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { utils } from '@ohif/core';
|
||||||
|
import React from 'react';
|
||||||
|
import DicomTagBrowser from './components/DicomTagBrowser';
|
||||||
|
|
||||||
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
|
export default function getCommandsModule(servicesManager) {
|
||||||
|
const actions = {
|
||||||
|
openDICOMTagViewer({ viewports }) {
|
||||||
|
const { activeViewportIndex, viewportSpecificData } = viewports;
|
||||||
|
const activeViewportSpecificData =
|
||||||
|
viewportSpecificData[activeViewportIndex];
|
||||||
|
|
||||||
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
displaySetInstanceUID,
|
||||||
|
} = activeViewportSpecificData;
|
||||||
|
|
||||||
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||||
|
const displaySets = studyMetadata.getDisplaySets();
|
||||||
|
|
||||||
|
const { UIModalService } = servicesManager.services;
|
||||||
|
|
||||||
|
const WrappedDicomTagBrowser = function() {
|
||||||
|
return (
|
||||||
|
<DicomTagBrowser
|
||||||
|
displaySets={displaySets}
|
||||||
|
displaySetInstanceUID={displaySetInstanceUID}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
UIModalService.show({
|
||||||
|
content: WrappedDicomTagBrowser,
|
||||||
|
title: `DICOM Tag Browser`,
|
||||||
|
fullscreen: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const definitions = {
|
||||||
|
openDICOMTagViewer: {
|
||||||
|
commandFn: actions.openDICOMTagViewer,
|
||||||
|
storeContexts: ['servers', 'viewports'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
definitions,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import getCommandsModule from './getCommandsModule';
|
||||||
|
import toolbarModule from './toolbarModule';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* Only required property. Should be a unique value across all extensions.
|
||||||
|
*/
|
||||||
|
id: 'dicom-tag-browser',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MODULE GETTERS
|
||||||
|
*/
|
||||||
|
getCommandsModule({ servicesManager }) {
|
||||||
|
return getCommandsModule(servicesManager);
|
||||||
|
},
|
||||||
|
getToolbarModule() {
|
||||||
|
return toolbarModule;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const TOOLBAR_BUTTON_TYPES = {
|
||||||
|
COMMAND: 'command',
|
||||||
|
};
|
||||||
|
|
||||||
|
const definitions = [
|
||||||
|
{
|
||||||
|
id: 'TagBrowser',
|
||||||
|
label: 'Tag Browser',
|
||||||
|
icon: 'list',
|
||||||
|
//
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'openDICOMTagViewer',
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
definitions,
|
||||||
|
defaultContext: 'VIEWER',
|
||||||
|
};
|
||||||
@@ -3,6 +3,14 @@
|
|||||||
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.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.2.0...@ohif/extension-lesion-tracker@0.2.1) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-lesion-tracker
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
|
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-lesion-tracker",
|
"name": "@ohif/extension-lesion-tracker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "OHIF extension for Lesion Tracker",
|
"description": "OHIF extension for Lesion Tracker",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
|
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
|
||||||
|
import { version } from '../package.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'lesion-tracker',
|
id: 'lesion-tracker',
|
||||||
|
version,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} params
|
* @param {object} params
|
||||||
@@ -26,7 +28,7 @@ export default {
|
|||||||
component: MeasurementComparisonTable,
|
component: MeasurementComparisonTable,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
defaultContext: ['VIEWER']
|
defaultContext: ['VIEWER'],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,248 @@
|
|||||||
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.7.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.8...@ohif/extension-vtk@1.7.9) (2020-10-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.7...@ohif/extension-vtk@1.7.8) (2020-09-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.6...@ohif/extension-vtk@1.7.7) (2020-09-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.5...@ohif/extension-vtk@1.7.6) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.4...@ohif/extension-vtk@1.7.5) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.3...@ohif/extension-vtk@1.7.4) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.2...@ohif/extension-vtk@1.7.3) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.1...@ohif/extension-vtk@1.7.2) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.0...@ohif/extension-vtk@1.7.1) (2020-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fail gracefully on an MPR load error ([#1992](https://github.com/OHIF/Viewers/issues/1992)) ([779a7e0](https://github.com/OHIF/Viewers/commit/779a7e0976aa3a2d2bf1dc8f056950f0545c9fff))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.10...@ohif/extension-vtk@1.7.0) (2020-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.9...@ohif/extension-vtk@1.6.10) (2020-08-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.8...@ohif/extension-vtk@1.6.9) (2020-07-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.7...@ohif/extension-vtk@1.6.8) (2020-07-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.6...@ohif/extension-vtk@1.6.7) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.5...@ohif/extension-vtk@1.6.6) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.4...@ohif/extension-vtk@1.6.5) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.3...@ohif/extension-vtk@1.6.4) (2020-06-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.2...@ohif/extension-vtk@1.6.3) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.1...@ohif/extension-vtk@1.6.2) (2020-06-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.6.0...@ohif/extension-vtk@1.6.1) (2020-06-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.6...@ohif/extension-vtk@1.6.0) (2020-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 1729 - error boundary wrapper ([#1764](https://github.com/OHIF/Viewers/issues/1764)) ([c02b232](https://github.com/OHIF/Viewers/commit/c02b232b0cc24f38af5d5e3831d987d048e60ada))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.5...@ohif/extension-vtk@1.5.6) (2020-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.4...@ohif/extension-vtk@1.5.5) (2020-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.3...@ohif/extension-vtk@1.5.4) (2020-05-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.2...@ohif/extension-vtk@1.5.3) (2020-05-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.1...@ohif/extension-vtk@1.5.2) (2020-05-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.5.0...@ohif/extension-vtk@1.5.1) (2020-04-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.4.1...@ohif/extension-vtk@1.5.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.4.0...@ohif/extension-vtk@1.4.1) (2020-04-23)
|
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.4.0...@ohif/extension-vtk@1.4.1) (2020-04-23)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-vtk
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.4.1",
|
"version": "1.7.9",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
"@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.2.8",
|
||||||
"cornerstone-tools": "4.12.5",
|
"cornerstone-tools": "^4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^3.1.0",
|
||||||
"dcmjs": "^0.10.1",
|
"dcmjs": "0.16.3",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"i18next": "^17.0.3",
|
"i18next": "^17.0.3",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
"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.8.3"
|
"react-vtkjs-viewport": "^0.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^2.8.1",
|
"@ohif/core": "^2.10.6",
|
||||||
"@ohif/ui": "^1.4.3",
|
"@ohif/ui": "^1.5.6",
|
||||||
"cornerstone-tools": "4.12.5",
|
"cornerstone-tools": "^4.20.1",
|
||||||
"cornerstone-wado-image-loader": "^3.1.0",
|
"cornerstone-wado-image-loader": "^3.1.0",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class LoadingIndicator extends PureComponent {
|
|||||||
<div className="imageViewerLoadingIndicator loadingIndicator">
|
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||||
<div className="indicatorContents">
|
<div className="indicatorContents">
|
||||||
<p>
|
<p>
|
||||||
{this.props.t('Reformatting')}...
|
{this.props.t('Loading...')}
|
||||||
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
||||||
{percComplete}
|
{percComplete}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -50,22 +50,25 @@ class OHIFVTKViewport extends Component {
|
|||||||
volumes: null,
|
volumes: null,
|
||||||
paintFilterLabelMapImageData: null,
|
paintFilterLabelMapImageData: null,
|
||||||
paintFilterBackgroundImageData: null,
|
paintFilterBackgroundImageData: null,
|
||||||
|
percentComplete: 0,
|
||||||
|
isLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
viewportData: PropTypes.shape({
|
viewportData: PropTypes.shape({
|
||||||
studies: PropTypes.array,
|
studies: PropTypes.array.isRequired,
|
||||||
displaySet: PropTypes.shape({
|
displaySet: PropTypes.shape({
|
||||||
StudyInstanceUID: PropTypes.string,
|
StudyInstanceUID: PropTypes.string.isRequired,
|
||||||
displaySetInstanceUID: PropTypes.string,
|
displaySetInstanceUID: PropTypes.string.isRequired,
|
||||||
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
||||||
SOPInstanceUID: PropTypes.string,
|
SOPInstanceUID: PropTypes.string,
|
||||||
frameIndex: PropTypes.number,
|
frameIndex: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
viewportIndex: PropTypes.number,
|
viewportIndex: PropTypes.number.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onScroll: PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
servicesManager: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -156,6 +159,12 @@ class OHIFVTKViewport extends Component {
|
|||||||
const { activeLabelmapIndex } = brushStackState;
|
const { activeLabelmapIndex } = brushStackState;
|
||||||
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
||||||
|
|
||||||
|
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
|
||||||
|
isHidden => {
|
||||||
|
return { visible: !isHidden };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
||||||
|
|
||||||
if (labelmapCache[vtkLabelmapID]) {
|
if (labelmapCache[vtkLabelmapID]) {
|
||||||
@@ -339,7 +348,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { displaySet } = this.props.viewportData;
|
const { displaySet } = this.props.viewportData;
|
||||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||||
|
|
||||||
@@ -356,34 +365,54 @@ class OHIFVTKViewport extends Component {
|
|||||||
loadProgressively(imageDataObject) {
|
loadProgressively(imageDataObject) {
|
||||||
loadImageData(imageDataObject);
|
loadImageData(imageDataObject);
|
||||||
|
|
||||||
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
const { isLoading, imageIds } = imageDataObject;
|
||||||
|
|
||||||
const NumberOfFrames = insertPixelDataPromises.length;
|
|
||||||
|
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
this.setState({ isLoaded: true });
|
this.setState({ isLoaded: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPixelDataPromises.forEach(promise => {
|
const NumberOfFrames = imageIds.length;
|
||||||
promise.then(numberProcessed => {
|
|
||||||
const percentComplete = Math.floor(
|
|
||||||
(numberProcessed * 100) / NumberOfFrames
|
|
||||||
);
|
|
||||||
|
|
||||||
if (percentComplete !== this.state.percentComplete) {
|
const onPixelDataInsertedCallback = numberProcessed => {
|
||||||
this.setState({
|
const percentComplete = Math.floor(
|
||||||
percentComplete,
|
(numberProcessed * 100) / NumberOfFrames
|
||||||
|
);
|
||||||
|
|
||||||
|
if (percentComplete !== this.state.percentComplete) {
|
||||||
|
this.setState({
|
||||||
|
percentComplete,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPixelDataInsertedErrorCallback = error => {
|
||||||
|
const { UINotificationService } = this.props.servicesManager.services;
|
||||||
|
|
||||||
|
if (!this.hasError) {
|
||||||
|
if (this.props.viewportIndex === 0) {
|
||||||
|
// Only show the notification from one viewport 1 in MPR2D.
|
||||||
|
UINotificationService.show({
|
||||||
|
title: 'MPR Load Error',
|
||||||
|
message: error.message,
|
||||||
|
type: 'error',
|
||||||
|
autoClose: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all(insertPixelDataPromises).then(() => {
|
this.hasError = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAllPixelDataInsertedCallback = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
imageDataObject.onPixelDataInserted(onPixelDataInsertedCallback);
|
||||||
|
imageDataObject.onAllPixelDataInserted(onAllPixelDataInsertedCallback);
|
||||||
|
imageDataObject.onPixelDataInsertedError(onPixelDataInsertedErrorCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -405,10 +434,6 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||||
|
|
||||||
const visible = configuration.renderFill || configuration.renderOutline;
|
|
||||||
const opacity = configuration.fillAlpha;
|
|
||||||
const outlineThickness = configuration.outlineThickness;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
@@ -428,10 +453,14 @@ class OHIFVTKViewport extends Component {
|
|||||||
dataDetails={this.state.dataDetails}
|
dataDetails={this.state.dataDetails}
|
||||||
labelmapRenderingOptions={{
|
labelmapRenderingOptions={{
|
||||||
colorLUT: this.state.labelmapColorLUT,
|
colorLUT: this.state.labelmapColorLUT,
|
||||||
globalOpacity: opacity,
|
globalOpacity: configuration.fillAlpha,
|
||||||
visible,
|
visible: configuration.renderFill,
|
||||||
outlineThickness,
|
outlineThickness: configuration.outlineWidth,
|
||||||
renderOutline: true,
|
renderOutline: configuration.renderOutline,
|
||||||
|
segmentsDefaultProperties: this.segmentsDefaultProperties,
|
||||||
|
onNewSegmentationRequested: () => {
|
||||||
|
this.setStateFromProps();
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onScroll={this.props.onScroll}
|
onScroll={this.props.onScroll}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import {
|
|||||||
vtkInteractorStyleMPRRotate,
|
vtkInteractorStyleMPRRotate,
|
||||||
vtkSVGCrosshairsWidget,
|
vtkSVGCrosshairsWidget,
|
||||||
} from 'react-vtkjs-viewport';
|
} from 'react-vtkjs-viewport';
|
||||||
|
import { getImageData } from 'react-vtkjs-viewport';
|
||||||
|
import { vec3 } from 'gl-matrix';
|
||||||
import setMPRLayout from './utils/setMPRLayout.js';
|
import setMPRLayout from './utils/setMPRLayout.js';
|
||||||
import setViewportToVTK from './utils/setViewportToVTK.js';
|
import setViewportToVTK from './utils/setViewportToVTK.js';
|
||||||
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
||||||
|
import OHIFVTKViewport from './OHIFVTKViewport';
|
||||||
|
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
|
||||||
|
|
||||||
const { BlendMode } = Constants;
|
const { BlendMode } = Constants;
|
||||||
|
|
||||||
@@ -34,7 +37,6 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = viewportSpecificData[activeViewportIndex];
|
const displaySet = viewportSpecificData[activeViewportIndex];
|
||||||
|
|
||||||
let api;
|
let api;
|
||||||
if (!api) {
|
if (!api) {
|
||||||
try {
|
try {
|
||||||
@@ -99,6 +101,20 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _convertModelToWorldSpace = (position, vtkImageData) => {
|
||||||
|
const indexToWorld = vtkImageData.getIndexToWorld();
|
||||||
|
const pos = vec3.create();
|
||||||
|
|
||||||
|
position[0] += 0.5; /* Move to the centre of the voxel. */
|
||||||
|
position[1] += 0.5; /* Move to the centre of the voxel. */
|
||||||
|
position[2] += 0.5; /* Move to the centre of the voxel. */
|
||||||
|
|
||||||
|
vec3.set(pos, position[0], position[1], position[2]);
|
||||||
|
vec3.transformMat4(pos, pos, indexToWorld);
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
getVtkApis: ({ index }) => {
|
getVtkApis: ({ index }) => {
|
||||||
return apis[index];
|
return apis[index];
|
||||||
@@ -124,6 +140,119 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
|
|
||||||
_setView(api, [0, 1, 0], [0, 0, 1]);
|
_setView(api, [0, 1, 0], [0, 0, 1]);
|
||||||
},
|
},
|
||||||
|
requestNewSegmentation: async ({ viewports }) => {
|
||||||
|
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||||
|
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||||
|
let api = apis[viewportIndex];
|
||||||
|
|
||||||
|
if (!api) {
|
||||||
|
api = await _getActiveViewportVTKApi(viewports);
|
||||||
|
apis[viewportIndex] = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.requestNewSegmentation();
|
||||||
|
api.updateImage();
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
},
|
||||||
|
jumpToSlice: async ({
|
||||||
|
viewports,
|
||||||
|
studies,
|
||||||
|
StudyInstanceUID,
|
||||||
|
displaySetInstanceUID,
|
||||||
|
SOPClassUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
segmentNumber,
|
||||||
|
frameIndex,
|
||||||
|
frame,
|
||||||
|
done = () => { }
|
||||||
|
}) => {
|
||||||
|
let api = apis[viewports.activeViewportIndex];
|
||||||
|
|
||||||
|
if (!api) {
|
||||||
|
api = await _getActiveViewportVTKApi(viewports);
|
||||||
|
apis[viewports.activeViewportIndex] = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||||
|
studies,
|
||||||
|
StudyInstanceUID,
|
||||||
|
displaySetInstanceUID,
|
||||||
|
SOPClassUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||||
|
|
||||||
|
let pixelIndex = 0;
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
const rows = imageDataObject.dimensions[1];
|
||||||
|
const cols = imageDataObject.dimensions[0];
|
||||||
|
|
||||||
|
for (let j = 0; j < rows; j++) {
|
||||||
|
for (let i = 0; i < cols; i++) {
|
||||||
|
// [i, j] =
|
||||||
|
const pixel = frame.pixelData[pixelIndex];
|
||||||
|
if (pixel === segmentNumber) {
|
||||||
|
x += i;
|
||||||
|
y += j;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
pixelIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x /= count;
|
||||||
|
y /= count;
|
||||||
|
|
||||||
|
const position = [x, y, frameIndex];
|
||||||
|
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
|
||||||
|
|
||||||
|
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
setSegmentationConfiguration: async ({
|
||||||
|
viewports,
|
||||||
|
globalOpacity,
|
||||||
|
visible,
|
||||||
|
renderOutline,
|
||||||
|
outlineThickness,
|
||||||
|
}) => {
|
||||||
|
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||||
|
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||||
|
let api = apis[viewportIndex];
|
||||||
|
|
||||||
|
if (!api) {
|
||||||
|
api = await _getActiveViewportVTKApi(viewports);
|
||||||
|
apis[viewportIndex] = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.setGlobalOpacity(globalOpacity);
|
||||||
|
api.setVisibility(visible);
|
||||||
|
api.setOutlineThickness(outlineThickness);
|
||||||
|
api.setOutlineRendering(renderOutline);
|
||||||
|
api.updateImage();
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
},
|
||||||
|
setSegmentConfiguration: async ({ viewports, visible, segmentNumber }) => {
|
||||||
|
const allViewports = Object.values(viewports.viewportSpecificData);
|
||||||
|
const promises = allViewports.map(async (viewport, viewportIndex) => {
|
||||||
|
let api = apis[viewportIndex];
|
||||||
|
|
||||||
|
if (!api) {
|
||||||
|
api = await _getActiveViewportVTKApi(viewports);
|
||||||
|
apis[viewportIndex] = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.setSegmentVisibility(segmentNumber, visible);
|
||||||
|
api.updateImage();
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
},
|
||||||
enableRotateTool: () => {
|
enableRotateTool: () => {
|
||||||
apis.forEach(api => {
|
apis.forEach(api => {
|
||||||
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
||||||
@@ -280,6 +409,26 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
window.vtkActions = actions;
|
window.vtkActions = actions;
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
|
requestNewSegmentation: {
|
||||||
|
commandFn: actions.requestNewSegmentation,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
jumpToSlice: {
|
||||||
|
commandFn: actions.jumpToSlice,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
setSegmentationConfiguration: {
|
||||||
|
commandFn: actions.setSegmentationConfiguration,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
setSegmentConfiguration: {
|
||||||
|
commandFn: actions.setSegmentConfiguration,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
axial: {
|
axial: {
|
||||||
commandFn: actions.axial,
|
commandFn: actions.axial,
|
||||||
storeContexts: ['viewports'],
|
storeContexts: ['viewports'],
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
import asyncComponent from './asyncComponent.js';
|
import asyncComponent from './asyncComponent.js';
|
||||||
import commandsModule from './commandsModule.js';
|
import commandsModule from './commandsModule.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import withCommandsManager from './withCommandsManager.js';
|
import withCommandsManager from './withCommandsManager.js';
|
||||||
|
import { version } from '../package.json';
|
||||||
// This feels weird
|
// This feels weird
|
||||||
// import loadLocales from './loadLocales';
|
// import loadLocales from './loadLocales';
|
||||||
|
|
||||||
@@ -14,9 +16,13 @@ const vtkExtension = {
|
|||||||
* Only required property. Should be a unique value across all extensions.
|
* Only required property. Should be a unique value across all extensions.
|
||||||
*/
|
*/
|
||||||
id: 'vtk',
|
id: 'vtk',
|
||||||
|
version,
|
||||||
|
|
||||||
getViewportModule({ commandsManager }) {
|
getViewportModule({ commandsManager, servicesManager }) {
|
||||||
return withCommandsManager(OHIFVTKViewport, commandsManager);
|
const ExtendedVTKViewport = props => (
|
||||||
|
<OHIFVTKViewport {...props} servicesManager={servicesManager} />
|
||||||
|
);
|
||||||
|
return withCommandsManager(ExtendedVTKViewport, commandsManager);
|
||||||
},
|
},
|
||||||
getToolbarModule() {
|
getToolbarModule() {
|
||||||
return toolbarModule;
|
return toolbarModule;
|
||||||
|
|||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
NODE_MODULES_CACHE="./node_modules"
|
||||||
|
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
|
||||||
|
|
||||||
|
echo "Running netlify-lerna-cache.sh"
|
||||||
|
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
|
||||||
|
|
||||||
|
cache_deps() {
|
||||||
|
PACKAGES=$(ls -1 $1)
|
||||||
|
|
||||||
|
for PKG in $PACKAGES
|
||||||
|
do
|
||||||
|
PKG_NODE_MODULES="$1/$PKG/node_modules"
|
||||||
|
if [ -d $PKG_NODE_MODULES ];
|
||||||
|
then
|
||||||
|
mv $PKG_NODE_MODULES $LERNA_CACHE/$PKG
|
||||||
|
echo "Cached node modules for $PKG"
|
||||||
|
else
|
||||||
|
echo "Unable to cache node modules for $PKG"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_deps platform
|
||||||
|
cache_deps extensions
|
||||||
|
cache_deps modes
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
NODE_MODULES_CACHE="./node_modules"
|
||||||
|
LERNA_CACHE="$NODE_MODULES_CACHE/lerna-cache"
|
||||||
|
|
||||||
|
echo "Running netlify-lerna-restore.sh"
|
||||||
|
mkdir -p "$NODE_MODULES_CACHE/lerna-cache"
|
||||||
|
echo "$NODE_MODULES_CACHE/lerna-cache/*"
|
||||||
|
|
||||||
|
restore_deps() {
|
||||||
|
PACKAGES=$(ls -1 $1)
|
||||||
|
|
||||||
|
for PKG in $PACKAGES
|
||||||
|
do
|
||||||
|
PKG_CACHE="$LERNA_CACHE/$PKG"
|
||||||
|
if [ -d $PKG_CACHE ];
|
||||||
|
then
|
||||||
|
mv $PKG_CACHE $1/$PKG/node_modules
|
||||||
|
echo "Restored node modules for $PKG"
|
||||||
|
else
|
||||||
|
echo "Unable to restore cache for $PKG"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_deps platform
|
||||||
|
restore_deps extensions
|
||||||
|
restore_deps modes
|
||||||
+6
-7
@@ -8,6 +8,11 @@
|
|||||||
# managed by CircleCI and deployed to our Google Hosting
|
# managed by CircleCI and deployed to our Google Hosting
|
||||||
#
|
#
|
||||||
|
|
||||||
|
[build]
|
||||||
|
base = ""
|
||||||
|
publish = ".netlify/www/"
|
||||||
|
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
||||||
|
|
||||||
# NODE_VERSION in root `.nvmrc` takes priority
|
# NODE_VERSION in root `.nvmrc` takes priority
|
||||||
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
||||||
[build.environment]
|
[build.environment]
|
||||||
@@ -16,19 +21,13 @@
|
|||||||
YARN_VERSION = "1.17.3"
|
YARN_VERSION = "1.17.3"
|
||||||
RUBY_VERSION = "2.6.2"
|
RUBY_VERSION = "2.6.2"
|
||||||
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
||||||
|
NETLIFY_RESTORE = "1"
|
||||||
|
|
||||||
# Production context: all deploys from the Production branch set in your site's
|
# Production context: all deploys from the Production branch set in your site's
|
||||||
# deploy contexts will inherit these settings.
|
# deploy contexts will inherit these settings.
|
||||||
[context.production]
|
[context.production]
|
||||||
ignore = "exit 0" # Never build production; We'll let our CI do that
|
ignore = "exit 0" # Never build production; We'll let our CI do that
|
||||||
|
|
||||||
# Deploy Preview context: all deploys generated from a pull/merge request will
|
|
||||||
# inherit these settings.
|
|
||||||
[context.deploy-preview]
|
|
||||||
base = ""
|
|
||||||
publish = ".netlify/www/"
|
|
||||||
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
|
||||||
|
|
||||||
[[headers]]
|
[[headers]]
|
||||||
# Define which paths this specific [[headers]] block will cover.
|
# Define which paths this specific [[headers]] block will cover.
|
||||||
for = "/*"
|
for = "/*"
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
"see-changed": "lerna changed",
|
"see-changed": "lerna changed",
|
||||||
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
|
"docs:publish": "chmod +x ./build-and-publish-docs.sh && ./build-and-publish-docs.sh",
|
||||||
"release": "yarn run lerna:version && yarn run lerna:publish",
|
"release": "yarn run lerna:version && yarn run lerna:publish",
|
||||||
|
"lerna:cache": "./netlify-lerna-cache.sh",
|
||||||
|
"lerna:restore": "./netlify-lerna-restore.sh",
|
||||||
"lerna:version": "npx lerna version prerelease --force-publish",
|
"lerna:version": "npx lerna version prerelease --force-publish",
|
||||||
"lerna:publish": "lerna publish from-package --canary --dist-tag canary",
|
"lerna:publish": "lerna publish from-package --canary --dist-tag canary",
|
||||||
"link-list": "npm ls --depth=0 --link=true"
|
"link-list": "npm ls --depth=0 --link=true"
|
||||||
|
|||||||
@@ -3,6 +3,215 @@
|
|||||||
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.10.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.5...@ohif/core@2.10.6) (2020-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use SeriesMetadata method instead of property in findMostRecentStructuredReport ([#2038](https://github.com/OHIF/Viewers/issues/2038)) ([91364db](https://github.com/OHIF/Viewers/commit/91364db8820efc86665db258d0044c16fb3e2c87)), closes [#1714](https://github.com/OHIF/Viewers/issues/1714)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.4...@ohif/core@2.10.5) (2020-09-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.3...@ohif/core@2.10.4) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.2...@ohif/core@2.10.3) (2020-09-03)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.1...@ohif/core@2.10.2) (2020-09-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.0...@ohif/core@2.10.1) (2020-08-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.14...@ohif/core@2.10.0) (2020-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update react-vtkjs-viewport usage to use requestPool ([#1984](https://github.com/OHIF/Viewers/issues/1984)) ([bb5f30c](https://github.com/OHIF/Viewers/commit/bb5f30ce2a0192d2e021beaaadfff22fd38e17b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.14](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.13...@ohif/core@2.9.14) (2020-08-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.13](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.12...@ohif/core@2.9.13) (2020-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Switch DICOMFileUploader to use the UIModalService ([#1904](https://github.com/OHIF/Viewers/issues/1904)) ([7772fee](https://github.com/OHIF/Viewers/commit/7772fee21ae6a65994e1251e2f1d2554b47781be))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.12](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.11...@ohif/core@2.9.12) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 - Put guards in all places that a cornerstone re-render ([#1899](https://github.com/OHIF/Viewers/issues/1899)) ([451f7ea](https://github.com/OHIF/Viewers/commit/451f7eab9258e7a193eb362e0926b13aedc4b3c9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.10...@ohif/core@2.9.11) (2020-07-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.9...@ohif/core@2.9.10) (2020-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix RT Panel hide/show and Fix looping load errors ([#1877](https://github.com/OHIF/Viewers/issues/1877)) ([e7cc735](https://github.com/OHIF/Viewers/commit/e7cc735c03d02eeb0d3af4ba02c15ed4f81bbec2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.8...@ohif/core@2.9.9) (2020-06-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.7...@ohif/core@2.9.8) (2020-06-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Disable seg panel when data for seg unavailable ([#1732](https://github.com/OHIF/Viewers/issues/1732)) ([698e900](https://github.com/OHIF/Viewers/commit/698e900b85121d3c2a46747c443ef69fb7a8c95b)), closes [#1728](https://github.com/OHIF/Viewers/issues/1728)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.6...@ohif/core@2.9.7) (2020-06-04)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Load default display set when no time metadata ([#1684](https://github.com/OHIF/Viewers/issues/1684)) ([f7b8b6a](https://github.com/OHIF/Viewers/commit/f7b8b6a41c4626084ef56b0fdf7363e914b143c4)), closes [#1683](https://github.com/OHIF/Viewers/issues/1683)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.4...@ohif/core@2.9.5) (2020-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.3...@ohif/core@2.9.4) (2020-05-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.2...@ohif/core@2.9.3) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.1...@ohif/core@2.9.2) (2020-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use bit-appropriate array for palette lookup tables ([#1698](https://github.com/OHIF/Viewers/issues/1698)) ([7033886](https://github.com/OHIF/Viewers/commit/70338866978a76fa026c18d7c3c05257c5ece762))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.9.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.0...@ohif/core@2.9.1) (2020-04-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.8.1...@ohif/core@2.9.0) (2020-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.8.0...@ohif/core@2.8.1) (2020-04-23)
|
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.8.0...@ohif/core@2.8.1) (2020-04-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user