Compare commits
127
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d426b78967 | ||
|
|
1d85f7a74e | ||
|
|
8833731ad3 | ||
|
|
bb7cce4716 | ||
|
|
4ad3ab93b0 | ||
|
|
3e074da64e | ||
|
|
d66bad664a | ||
|
|
fa47db4a54 | ||
|
|
e1c21a7ce1 | ||
|
|
c785a5e975 | ||
|
|
f133be75d1 | ||
|
|
b880663818 | ||
|
|
e160889605 | ||
|
|
2f0d1b651b | ||
|
|
d034006294 | ||
|
|
2f459a0f64 | ||
|
|
ee80e02661 | ||
|
|
4e53fbfe9d | ||
|
|
64dfbeab7a | ||
|
|
9c1112f768 | ||
|
|
9b3be9b0c0 | ||
|
|
6829542c00 | ||
|
|
846c9c1c54 | ||
|
|
85fed49eb1 | ||
|
|
298da03e2f | ||
|
|
141e1cf648 | ||
|
|
ea2ab1a25e | ||
|
|
3d0357c9c0 | ||
|
|
f68aaf973c | ||
|
|
f04d459a63 | ||
|
|
4cf9f2c9cb | ||
|
|
c0cc9752c6 | ||
|
|
2910d38668 | ||
|
|
6c950a9669 | ||
|
|
06075ecb51 | ||
|
|
509a7b7e57 | ||
|
|
4bbf24b250 | ||
|
|
ebc01a8ca2 | ||
|
|
4fd0c6dfcb | ||
|
|
06a4af06fa | ||
|
|
6cba41dd8b | ||
|
|
2f30e7a821 | ||
|
|
9a62c28b3f | ||
|
|
98a649b455 | ||
|
|
3529474a16 | ||
|
|
0c37a406d9 | ||
|
|
e8d56c11f0 | ||
|
|
a6b92a4505 | ||
|
|
a4c9a86772 | ||
|
|
73e440968c | ||
|
|
3f7eb8a5d3 | ||
|
|
7b2d44f2c1 | ||
|
|
225da81135 | ||
|
|
686d12da5c | ||
|
|
c22756051b | ||
|
|
8fdfcc0c7d | ||
|
|
44ab65c981 | ||
|
|
b9574b6efc | ||
|
|
96a8e26786 | ||
|
|
80f5b23622 | ||
|
|
2b4950e954 | ||
|
|
e16f14089d | ||
|
|
c3807a89ed | ||
|
|
049ef4a666 | ||
|
|
d5fa880a29 | ||
|
|
1b9a6db73f | ||
|
|
ed2692908e | ||
|
|
0c6c5a5d51 | ||
|
|
b449f8eb0e | ||
|
|
cf5160fb13 | ||
|
|
4c1c91c006 | ||
|
|
9ed39c149b | ||
|
|
b3f0fe029d | ||
|
|
a1b78afdb9 | ||
|
|
3d3cfb1b41 | ||
|
|
b812fe03d2 | ||
|
|
84f087bca4 | ||
|
|
a7e612eb88 | ||
|
|
9e3afda2f1 | ||
|
|
4d7a91a584 | ||
|
|
1dc320edcd | ||
|
|
1d02f5485f | ||
|
|
4c9a977920 | ||
|
|
b5db09c25e | ||
|
|
8dc4a5b82a | ||
|
|
44add9ce2f | ||
|
|
c19eceeede | ||
|
|
72bc5a5ae5 | ||
|
|
dfdf46dbdd | ||
|
|
571dff2c09 | ||
|
|
b9a66d4424 | ||
|
|
7ab2e3e60d | ||
|
|
a5d6bc6a51 | ||
|
|
df612ca4a0 | ||
|
|
a91ce57c43 | ||
|
|
342884f83e | ||
|
|
0fb39bad84 | ||
|
|
c74c80bb6f | ||
|
|
910d5c65d5 | ||
|
|
d0fb4843ab | ||
|
|
f488b857fe | ||
|
|
256e8b1116 | ||
|
|
168dd87747 | ||
|
|
13c41e6d86 | ||
|
|
7ab0bbb325 | ||
|
|
2213187b14 | ||
|
|
4c4d8a61b6 | ||
|
|
67f58f5f09 | ||
|
|
dfc35885bb | ||
|
|
e9c449d1db | ||
|
|
6dac50c7c6 | ||
|
|
9be54f6164 | ||
|
|
341cc5a4eb | ||
|
|
7b80171203 | ||
|
|
2e5a385e53 | ||
|
|
a3caeb381e | ||
|
|
ab0af86379 | ||
|
|
86426cb024 | ||
|
|
348840ef26 | ||
|
|
52ef934961 | ||
|
|
be995f93b6 | ||
|
|
5036615e00 | ||
|
|
4ee2b135a7 | ||
|
|
388fe04e09 | ||
|
|
2e215aed4c | ||
|
|
ffd27d6db4 | ||
|
|
6540e36818 |
No files matched your search
@@ -328,6 +328,7 @@ workflows:
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
@@ -352,7 +353,7 @@ workflows:
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*'
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
||||
wait-on: 'http://localhost:3000'
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
||||
if [ ! -z "$CLIENT_ID" ]
|
||||
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||
then
|
||||
echo "Google Cloud Healthcare $CLIENT_ID has been provided: "
|
||||
echo $CLIENT_ID
|
||||
echo "Updating config..."
|
||||
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
||||
if [ -n "$CLIENT_ID" ]
|
||||
then
|
||||
echo "Google Cloud Healthcare \$CLIENT_ID has been provided: "
|
||||
echo "$CLIENT_ID"
|
||||
echo "Updating config..."
|
||||
|
||||
# - Use SED to replace the CLIENT_ID that is currently in google.js
|
||||
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
|
||||
# - Use SED to replace the CLIENT_ID that is currently in google.js
|
||||
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
|
||||
fi
|
||||
|
||||
# - Copy google.js to overwrite app-config.js
|
||||
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||
# If HEALTHCARE_API_ENDPOINT is specified, use the google.js configuration with the modified endpoint
|
||||
if [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||
then
|
||||
echo "Google Cloud Healthcare \$HEALTHCARE_API_ENDPOINT has been provided: "
|
||||
echo "$HEALTHCARE_API_ENDPOINT"
|
||||
echo "Updating config..."
|
||||
|
||||
# - Use SED to replace the HEALTHCARE_API_ENDPOINT that is currently in google.js
|
||||
sed -i -e "s+https://healthcare.googleapis.com/v1beta1+$HEALTHCARE_API_ENDPOINT+g" /usr/share/nginx/html/google.js
|
||||
fi
|
||||
|
||||
# - Copy google.js to overwrite app-config.js
|
||||
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||
fi
|
||||
|
||||
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||
|
||||
+2
-1
@@ -12,10 +12,11 @@ exemptLabels:
|
||||
- 'Task: CI/Tooling :robot:'
|
||||
- 'Task: Docs 📖'
|
||||
- 'Task: Docs :book:'
|
||||
- 'Task: Refactor 🛠'
|
||||
- 'Task: Refactor :hammer_and_wrench:'
|
||||
- 'Task: Tests :microscope:'
|
||||
- 'PR: Awaiting Review 👀'
|
||||
- 'Triage :white_flag:'
|
||||
- 'Extension: Discussion'
|
||||
- 'Announcement 🎉'
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: 'Stale :baguette_bread:'
|
||||
|
||||
@@ -30,6 +30,7 @@ docker/dcm4che/dcm4che-arc
|
||||
|
||||
# Cypress test results
|
||||
videos/
|
||||
screenshots/
|
||||
|
||||
# Locize settings
|
||||
.locize
|
||||
Vendored
+4
-1
@@ -28,5 +28,8 @@
|
||||
],
|
||||
"prettier.disableLanguages": ["html"],
|
||||
"prettier.endOfLine": "lf",
|
||||
"workbench.colorCustomizations": {}
|
||||
"workbench.colorCustomizations": {},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,10 @@ export default {
|
||||
from: 'right',
|
||||
// The target component to toggle open/close
|
||||
target: 'target-component-id',
|
||||
// UI Hint; If the target panel is in a "disabled" state
|
||||
isDisabled: studies => {
|
||||
return false;
|
||||
},
|
||||
// Overrides `defaultContext`, if specified
|
||||
context: ['ACTIVE_VIEWPORT:MAGIC'],
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ An extension can register a SOP Class Handler Module by defining a
|
||||
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
|
||||
own components. It instead defines:
|
||||
|
||||
- `sopClassUids`: an array of string SOP Class UIDs that the
|
||||
- `sopClassUIDs`: an array of string SOP Class UIDs that the
|
||||
`getDisplaySetFromSeries` method should be applied to.
|
||||
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
||||
display set
|
||||
@@ -15,13 +15,13 @@ A `displaySet` has the following shape:
|
||||
```js
|
||||
return {
|
||||
plugin: 'html',
|
||||
modality: 'SR',
|
||||
displaySetInstanceUid: 0,
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
```
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUids: Object.values(SOP_CLASS_UIDS),
|
||||
sopClassUIDs: Object.values(SOP_CLASS_UIDS),
|
||||
|
||||
/**
|
||||
* @param {object} series -
|
||||
@@ -63,12 +63,12 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUid: 0,
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Lesion Tracker - Audit Trail
|
||||
|
||||
1. To view Audit Trails, select **View Audit Log** on the Configuration menu.
|
||||
2. To filter audit logs for a column or multiple columns, type or select in the related column or columns.
|
||||
2. To filter audit logs for a column or multiple columns, type or select in the
|
||||
related column or columns.
|
||||
|
||||

|
||||
@@ -2,50 +2,74 @@
|
||||
|
||||

|
||||
|
||||
1. Study List allows to sort each column. To sort studies, click the desired column header.
|
||||
2. Study List is filterable for each column and you can filter by more than one column by typing in the column field. To filter a column, type the text in the desired column header field and press Enter.
|
||||
3. Pagination is provided for the Study List at the bottom of the page. You can change the number of rows per page or the current page.
|
||||
1. Study List allows to sort each column. To sort studies, click the desired
|
||||
column header.
|
||||
2. Study List is filterable for each column and you can filter by more than one
|
||||
column by typing in the column field. To filter a column, type the text in
|
||||
the desired column header field and press Enter.
|
||||
3. Pagination is provided for the Study List at the bottom of the page. You can
|
||||
change the number of rows per page or the current page.
|
||||
|
||||
## Study List Context Menu
|
||||
|
||||
When you right-click on the study row, Study Context Menu will pop up. Study Context Menu includes operations at the study level.
|
||||
When you right-click on the study row, Study Context Menu will pop up. Study
|
||||
Context Menu includes operations at the study level.
|
||||
|
||||
There are a couple of ways to view a study:
|
||||
|
||||
* Simply double-click on the study row.
|
||||
* Select **View** option on the Study Context Menu, then you will be redirected to Viewer page.
|
||||
- Simply double-click on the study row.
|
||||
- Select **View** option on the Study Context Menu, then you will be redirected
|
||||
to Viewer page.
|
||||
|
||||

|
||||
|
||||
### Timepoint Association
|
||||
|
||||
1. To link a study with a timepoint, right-click on the study row and select **Associate** option on the Study Context Menu. You will see Study Association dialog.
|
||||
2. To link multiple studies at the same time, hold Shift key and select studies you would like to associate, then right-click and select Associate on the Study Context Menu
|
||||
1. To link a study with a timepoint, right-click on the study row and select
|
||||
**Associate** option on the Study Context Menu. You will see Study
|
||||
Association dialog.
|
||||
2. To link multiple studies at the same time, hold Shift key and select studies
|
||||
you would like to associate, then right-click and select Associate on the
|
||||
Study Context Menu
|
||||
|
||||

|
||||
|
||||
The Study Association dialog allows you to select and unselect Timepoint Type for the selected study or studies. Also, it will include all studies within 14 days of your selected studies, in case you forgot to select a study. After you identify timepoints for the selected study or studies, click **Save** to save the association.
|
||||
The Study Association dialog allows you to select and unselect Timepoint Type
|
||||
for the selected study or studies. Also, it will include all studies within 14
|
||||
days of your selected studies, in case you forgot to select a study. After you
|
||||
identify timepoints for the selected study or studies, click **Save** to save
|
||||
the association.
|
||||
|
||||

|
||||
|
||||
### Remove Timepoint Association
|
||||
To unlink a timepoint association with a study, right-click on the study row and select **Remove Association** option on the Study Context Menu. Remove Association will be disabled if the study is already dissociated.
|
||||
|
||||
To unlink a timepoint association with a study, right-click on the study row and
|
||||
select **Remove Association** option on the Study Context Menu. Remove
|
||||
Association will be disabled if the study is already dissociated.
|
||||
|
||||

|
||||
|
||||
### View Series Details
|
||||
To view details of related series, right-click on the study row and select **View Series Details** option on the Study Context Menu, then Series Details dialog will pop up. Series Details dialog gives a summary of the selected study.
|
||||
|
||||
To view details of related series, right-click on the study row and select
|
||||
**View Series Details** option on the Study Context Menu, then Series Details
|
||||
dialog will pop up. Series Details dialog gives a summary of the selected study.
|
||||
|
||||

|
||||
|
||||
### Anonymize (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Send (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Export (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Delete (Under Development)
|
||||
|
||||
...
|
||||
@@ -21,7 +21,6 @@ export default {
|
||||
/**
|
||||
* MODULE GETTERS
|
||||
*/
|
||||
|
||||
getViewportModule() {
|
||||
return '... react component ...';
|
||||
},
|
||||
@@ -64,7 +63,7 @@ const commandsModule = {
|
||||
*/
|
||||
const sopClassHandlerModule = {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUids: Object.values({
|
||||
sopClassUIDs: Object.values({
|
||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||
@@ -78,12 +77,12 @@ const sopClassHandlerModule = {
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUid: 0, //utils.guid(),
|
||||
displaySetInstanceUID: 0, //utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
@@ -98,6 +97,9 @@ const panelModule = {
|
||||
icon: 'th-list',
|
||||
label: 'Segments',
|
||||
target: 'segment-panel',
|
||||
isDisabled: studies => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
],
|
||||
components: [
|
||||
|
||||
@@ -3,6 +3,88 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.2...@ohif/extension-cornerstone@2.4.3) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.1...@ohif/extension-cornerstone@2.4.2) (2020-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove Eraser and ROI Window ([6c950a9](https://github.com/OHIF/Viewers/commit/6c950a9669f7fbf3c46e48679fa26ee514824156))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.0...@ohif/extension-cornerstone@2.4.1) (2020-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bump react-cornerstone-viewport version to address critical issue ([#1473](https://github.com/OHIF/Viewers/issues/1473)) ([ee80e02](https://github.com/OHIF/Viewers/commit/ee80e026610442e94caf5e4e3e4d193220cd0ece))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.3.1...@ohif/extension-cornerstone@2.4.0) (2020-02-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* [#1342](https://github.com/OHIF/Viewers/issues/1342) - Window level tab ([#1429](https://github.com/OHIF/Viewers/issues/1429)) ([ebc01a8](https://github.com/OHIF/Viewers/commit/ebc01a8ca238d5a3437b44d81f75aa8a5e8d0574))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.3.0...@ohif/extension-cornerstone@2.3.1) (2020-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Creating 2 commands to activate zoom tool and also to move between displaySets ([#1446](https://github.com/OHIF/Viewers/issues/1446)) ([06a4af0](https://github.com/OHIF/Viewers/commit/06a4af06faaecf6fa06ccd90cdfa879ee8d53053))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.2...@ohif/extension-cornerstone@2.3.0) (2020-02-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 MeasurementService ([#1314](https://github.com/OHIF/Viewers/issues/1314)) ([0c37a40](https://github.com/OHIF/Viewers/commit/0c37a406d963569af8c3be24c697dafd42712dfc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.1...@ohif/extension-cornerstone@2.2.2) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.1...@ohif/extension-cornerstone@2.2.0) (2019-12-20)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("../../babel.config.js");
|
||||
@@ -0,0 +1,13 @@
|
||||
const base = require('../../jest.config.base.js');
|
||||
const pkg = require('./package');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
name: pkg.name,
|
||||
displayName: pkg.name,
|
||||
// rootDir: "../.."
|
||||
// testMatch: [
|
||||
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
|
||||
// "<rootDir>/platform/viewer/**/*.test.js"
|
||||
// ]
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.2.0",
|
||||
"version": "2.4.3",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -25,16 +25,18 @@
|
||||
"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"
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.9.0",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@@ -49,7 +51,6 @@
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"query-string": "^6.8.3",
|
||||
"react-cornerstone-viewport": "2.x.x"
|
||||
"react-cornerstone-viewport": "^2.3.6"
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,6 @@ import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
// Metadata configuration
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
@@ -58,34 +49,34 @@ class OHIFCornerstoneViewport extends Component {
|
||||
* Obtain the CornerstoneTools Stack for the specified display set.
|
||||
*
|
||||
* @param {Object[]} studies
|
||||
* @param {String} studyInstanceUid
|
||||
* @param {String} displaySetInstanceUid
|
||||
* @param {String} [sopInstanceUid]
|
||||
* @param {String} StudyInstanceUID
|
||||
* @param {String} displaySetInstanceUID
|
||||
* @param {String} [SOPInstanceUID]
|
||||
* @param {Number} [frameIndex=1]
|
||||
* @return {Object} CornerstoneTools Stack
|
||||
*/
|
||||
static getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex = 0
|
||||
) {
|
||||
if (!studies || !studies.length) {
|
||||
throw new Error('Studies not provided.');
|
||||
}
|
||||
|
||||
if (!studyInstanceUid) {
|
||||
if (!StudyInstanceUID) {
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
if (!displaySetInstanceUid) {
|
||||
if (!displaySetInstanceUID) {
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
// Create shortcut to displaySet
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
@@ -93,7 +84,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
}
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
if (!displaySet) {
|
||||
@@ -107,17 +98,14 @@ class OHIFCornerstoneViewport extends Component {
|
||||
const stack = Object.assign({}, storedStack);
|
||||
stack.currentImageIdIndex = frameIndex;
|
||||
|
||||
if (sopInstanceUid) {
|
||||
if (SOPInstanceUID) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
'sopCommonModule',
|
||||
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
||||
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||
});
|
||||
|
||||
if (index > -1) {
|
||||
@@ -134,39 +122,39 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
getViewportData = async (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
let viewportData;
|
||||
|
||||
switch (sopClassUid) {
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
const specialCaseHandler =
|
||||
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
||||
|
||||
viewportData = await specialCaseHandler(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
viewportData = {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
|
||||
@@ -179,31 +167,31 @@ class OHIFCornerstoneViewport extends Component {
|
||||
setStateFromProps() {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
sopClassUIDs,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
||||
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
if (sopClassUIDs && sopClassUIDs.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
'More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids && sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs && sopClassUIDs[0];
|
||||
|
||||
this.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
).then(viewportData => {
|
||||
this.setState({
|
||||
@@ -221,9 +209,9 @@ class OHIFCornerstoneViewport extends Component {
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUid !==
|
||||
prevDisplaySet.displaySetInstanceUid ||
|
||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
this.setStateFromProps();
|
||||
|
||||
@@ -2,6 +2,7 @@ import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import OHIF from '@ohif/core';
|
||||
|
||||
import setCornerstoneLayout from './utils/setCornerstoneLayout.js';
|
||||
import { getEnabledElement } from './state';
|
||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||
const scroll = cornerstoneTools.import('util/scroll');
|
||||
@@ -73,10 +74,6 @@ const commandsModule = ({ servicesManager }) => {
|
||||
}
|
||||
cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 });
|
||||
},
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
clearAnnotations: ({ viewports }) => {
|
||||
const element = getEnabledElement(viewports.activeViewportIndex);
|
||||
if (!element) {
|
||||
@@ -242,6 +239,22 @@ const commandsModule = ({ servicesManager }) => {
|
||||
cornerstoneTools.removeToolState(element, toolType, tool);
|
||||
cornerstone.updateImage(element);
|
||||
},
|
||||
setCornerstoneLayout: () => {
|
||||
setCornerstoneLayout();
|
||||
},
|
||||
setWindowLevel: ({ viewports, window, level }) => {
|
||||
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
|
||||
viewport.voi = {
|
||||
windowWidth: Number(window),
|
||||
windowCenter: Number(level),
|
||||
};
|
||||
cornerstone.setViewport(enabledElement, viewport);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -330,24 +343,28 @@ const commandsModule = ({ servicesManager }) => {
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
// TODO: First/Last image
|
||||
// Next/Previous series/DisplaySet
|
||||
nextViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: 1 },
|
||||
},
|
||||
previousViewportDisplaySet: {
|
||||
commandFn: actions.updateViewportDisplaySet,
|
||||
storeContexts: [],
|
||||
options: { direction: -1 },
|
||||
},
|
||||
// TOOLS
|
||||
setToolActive: {
|
||||
commandFn: actions.setToolActive,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
setZoomTool: {
|
||||
commandFn: actions.setToolActive,
|
||||
storeContexts: [],
|
||||
options: { toolName: 'Zoom' },
|
||||
},
|
||||
setCornerstoneLayout: {
|
||||
commandFn: actions.setCornerstoneLayout,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
context: 'VIEWER',
|
||||
},
|
||||
setWindowLevel: {
|
||||
commandFn: actions.setWindowLevel,
|
||||
storeContexts: ['viewports'],
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,25 +6,25 @@ import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { StackManager, DicomLoaderService } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.seriesInstanceUid === seriesInstanceUid;
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ function parseSeg(arrayBuffer, imageIds) {
|
||||
function addSegMetadataToCornerstoneToolState(
|
||||
segMetadata,
|
||||
toolState,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
toolState
|
||||
@@ -48,22 +48,22 @@ function addSegMetadataToCornerstoneToolState(
|
||||
const brushModule = cornerstoneTools.store.modules.brush;
|
||||
|
||||
for (let i = 0; i < segMetadata.length; i++) {
|
||||
brushModule.setters.metadata(displaySetInstanceUid, i, segMetadata[i]);
|
||||
brushModule.setters.metadata(displaySetInstanceUID, i, segMetadata[i]);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
@@ -77,16 +77,16 @@ async function handleSegmentationStorage(
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same seriesInstanceUid. This is not supported yet...'
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,8 +119,8 @@ async function handleSegmentationStorage(
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,27 +3,8 @@ import { SimpleDialog } from '@ohif/ui';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import merge from 'lodash.merge';
|
||||
import queryString from 'query-string';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
|
||||
function fallbackMetaDataProvider(type, imageId) {
|
||||
if (!imageId.includes('wado?requestType=WADO')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If you call for an WADO-URI imageId and get no
|
||||
// metadata, try reformatting to WADO-RS imageId
|
||||
const qs = queryString.parse(imageId);
|
||||
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot;
|
||||
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${
|
||||
qs.seriesUID
|
||||
}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
|
||||
|
||||
return cornerstone.metaData.get(type, wadoRsImageId);
|
||||
}
|
||||
|
||||
// Add this fallback provider with a low priority so it is handled last
|
||||
cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,9 +13,9 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ servicesManager, configuration }) {
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
const { UIDialogService, MeasurementService } = servicesManager.services;
|
||||
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
if (UIDialogService) {
|
||||
let dialogId = UIDialogService.create({
|
||||
centralize: true,
|
||||
@@ -57,16 +38,13 @@ export default function init({ servicesManager, configuration }) {
|
||||
};
|
||||
|
||||
const { csToolsConfig } = configuration;
|
||||
const { StackManager } = OHIF.utils;
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||
|
||||
// ~~ Set our MetadataProvider
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
metadataProvider.get.bind(metadataProvider),
|
||||
9999
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
// ~~
|
||||
const defaultCsToolsConfig = csToolsConfig || {
|
||||
globalToolSyncEnabled: true,
|
||||
@@ -80,7 +58,6 @@ export default function init({ servicesManager, configuration }) {
|
||||
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
||||
annotations: [
|
||||
csTools.ArrowAnnotateTool,
|
||||
csTools.EraserTool,
|
||||
csTools.BidirectionalTool,
|
||||
csTools.LengthTool,
|
||||
csTools.AngleTool,
|
||||
@@ -98,6 +75,7 @@ export default function init({ servicesManager, configuration }) {
|
||||
csTools.MagnifyTool,
|
||||
csTools.StackScrollTool,
|
||||
csTools.StackScrollMouseWheelTool,
|
||||
csTools.OverlayTool,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -106,6 +84,9 @@ export default function init({ servicesManager, configuration }) {
|
||||
tools.push(...toolsGroupedByType[toolsGroup])
|
||||
);
|
||||
|
||||
/* Measurement Service */
|
||||
_connectToolsToMeasurementService(MeasurementService);
|
||||
|
||||
/* Add extension tools configuration here. */
|
||||
const internalToolsConfig = {
|
||||
ArrowAnnotate: {
|
||||
@@ -179,4 +160,118 @@ export default function init({ servicesManager, configuration }) {
|
||||
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
|
||||
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
|
||||
csTools.setToolActive('ZoomTouchPinch', {});
|
||||
csTools.setToolEnabled('Overlay', {});
|
||||
}
|
||||
|
||||
const _initMeasurementService = measurementService => {
|
||||
/* Initialization */
|
||||
const { toAnnotation, toMeasurement } = measurementServiceMappingsFactory(
|
||||
measurementService
|
||||
);
|
||||
const csToolsVer4MeasurementSource = measurementService.createSource(
|
||||
'CornerstoneTools',
|
||||
'4'
|
||||
);
|
||||
|
||||
/* Matching Criterias */
|
||||
const matchingCriteria = {
|
||||
valueType: measurementService.VALUE_TYPES.POLYLINE,
|
||||
points: 2,
|
||||
};
|
||||
|
||||
/* Mappings */
|
||||
measurementService.addMapping(
|
||||
csToolsVer4MeasurementSource,
|
||||
'Length',
|
||||
matchingCriteria,
|
||||
toAnnotation,
|
||||
toMeasurement
|
||||
);
|
||||
|
||||
return csToolsVer4MeasurementSource;
|
||||
};
|
||||
|
||||
const _connectToolsToMeasurementService = measurementService => {
|
||||
const csToolsVer4MeasurementSource = _initMeasurementService(
|
||||
measurementService
|
||||
);
|
||||
const {
|
||||
id: sourceId,
|
||||
addOrUpdate,
|
||||
getAnnotation,
|
||||
} = csToolsVer4MeasurementSource;
|
||||
|
||||
/* Measurement Service Events */
|
||||
cornerstone.events.addEventListener(
|
||||
cornerstone.EVENTS.ELEMENT_ENABLED,
|
||||
event => {
|
||||
const {
|
||||
MEASUREMENT_ADDED,
|
||||
MEASUREMENT_UPDATED,
|
||||
} = measurementService.EVENTS;
|
||||
|
||||
measurementService.subscribe(
|
||||
MEASUREMENT_ADDED,
|
||||
({ source, measurement }) => {
|
||||
if (![sourceId].includes(source.id)) {
|
||||
const annotation = getAnnotation('Length', measurement.id);
|
||||
|
||||
console.log(
|
||||
'Measurement Service [Cornerstone]: Measurement added',
|
||||
measurement
|
||||
);
|
||||
console.log('Mapped annotation:', annotation);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
measurementService.subscribe(
|
||||
MEASUREMENT_UPDATED,
|
||||
({ source, measurement }) => {
|
||||
if (![sourceId].includes(source.id)) {
|
||||
const annotation = getAnnotation('Length', measurement.id);
|
||||
|
||||
console.log(
|
||||
'Measurement Service [Cornerstone]: Measurement updated',
|
||||
measurement
|
||||
);
|
||||
console.log('Mapped annotation:', annotation);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const addOrUpdateMeasurement = csToolsAnnotation => {
|
||||
try {
|
||||
const { toolName, toolType, measurementData } = csToolsAnnotation;
|
||||
const csTool = toolName || measurementData.toolType || toolType;
|
||||
csToolsAnnotation.id = measurementData._measurementServiceId;
|
||||
const measurementServiceId = addOrUpdate(csTool, csToolsAnnotation);
|
||||
|
||||
if (!measurementData._measurementServiceId) {
|
||||
addMeasurementServiceId(measurementServiceId, csToolsAnnotation);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to add or update measurement:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addMeasurementServiceId = (id, csToolsAnnotation) => {
|
||||
const { measurementData } = csToolsAnnotation;
|
||||
Object.assign(measurementData, { _measurementServiceId: id });
|
||||
};
|
||||
|
||||
[
|
||||
csTools.EVENTS.MEASUREMENT_ADDED,
|
||||
csTools.EVENTS.MEASUREMENT_MODIFIED,
|
||||
].forEach(csToolsEvtName => {
|
||||
event.detail.element.addEventListener(
|
||||
csToolsEvtName,
|
||||
({ detail: csToolsAnnotation }) => {
|
||||
console.log(`Cornerstone Element Event: ${csToolsEvtName}`);
|
||||
addOrUpdateMeasurement(csToolsAnnotation);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -215,15 +215,6 @@ const definitions = [
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Bidirectional' },
|
||||
},
|
||||
{
|
||||
id: 'Eraser',
|
||||
label: 'Eraser',
|
||||
icon: 'eraser',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Eraser' },
|
||||
},
|
||||
{
|
||||
id: 'Download',
|
||||
label: 'Download',
|
||||
@@ -237,6 +228,15 @@ const definitions = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Exit2DMPR',
|
||||
label: 'Exit 2D MPR',
|
||||
icon: 'times',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'setCornerstoneLayout',
|
||||
context: 'ACTIVE_VIEWPORT::VTK',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
const SUPPORTED_TOOLS = [
|
||||
'Length',
|
||||
'EllipticalRoi',
|
||||
'RectangleRoi',
|
||||
'ArrowAnnotate',
|
||||
];
|
||||
|
||||
const measurementServiceMappingsFactory = measurementService => {
|
||||
/**
|
||||
* Maps measurement service format object to cornerstone annotation object.
|
||||
*
|
||||
* @param {Measurement} measurement The measurement instance
|
||||
* @param {string} definition The source definition
|
||||
* @return {Object} Cornerstone annotation data
|
||||
*/
|
||||
const toAnnotation = (measurement, definition) => {
|
||||
const {
|
||||
id,
|
||||
label,
|
||||
description,
|
||||
points,
|
||||
unit,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID,
|
||||
} = measurement;
|
||||
|
||||
return {
|
||||
toolName: definition,
|
||||
measurementData: {
|
||||
sopInstanceUid: SOPInstanceUID,
|
||||
frameOfReferenceUID: FrameOfReferenceUID,
|
||||
SeriesInstanceUID: referenceSeriesUID,
|
||||
unit,
|
||||
text: label,
|
||||
description,
|
||||
handles: _getHandlesFromPoints(points),
|
||||
_measurementServiceId: id,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps cornerstone annotation event data to measurement service format.
|
||||
*
|
||||
* @param {Object} cornerstone Cornerstone event data
|
||||
* @return {Measurement} Measurement instance
|
||||
*/
|
||||
const toMeasurement = csToolsAnnotation => {
|
||||
const { element, measurementData } = csToolsAnnotation;
|
||||
const tool =
|
||||
csToolsAnnotation.toolType ||
|
||||
csToolsAnnotation.toolName ||
|
||||
measurementData.toolType;
|
||||
|
||||
const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName);
|
||||
|
||||
if (!validToolType(tool)) {
|
||||
throw new Error('Tool not supported');
|
||||
}
|
||||
|
||||
const {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
} = _getAttributes(element);
|
||||
|
||||
const points = [];
|
||||
points.push(measurementData.handles);
|
||||
|
||||
return {
|
||||
id: measurementData._measurementServiceId,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
label: measurementData.text,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
area:
|
||||
measurementData.cachedStats &&
|
||||
measurementData.cachedStats
|
||||
.area /* TODO: Add concept names instead (descriptor) */,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points: _getPointsFromHandles(measurementData.handles),
|
||||
};
|
||||
};
|
||||
|
||||
const _getAttributes = element => {
|
||||
const enabledElement = cornerstone.getEnabledElement(element);
|
||||
const imageId = enabledElement.image.imageId;
|
||||
const instance = cornerstone.metaData.get('instance', imageId);
|
||||
|
||||
return {
|
||||
SOPInstanceUID: instance.SOPInstanceUID,
|
||||
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||
};
|
||||
};
|
||||
|
||||
const _getValueTypeFromToolType = toolType => {
|
||||
const { POLYLINE, ELLIPSE, POINT } = measurementService.VALUE_TYPES;
|
||||
|
||||
/* TODO: Relocate static value types */
|
||||
const TOOL_TYPE_TO_VALUE_TYPE = {
|
||||
Length: POLYLINE,
|
||||
EllipticalRoi: ELLIPSE,
|
||||
RectangleRoi: POLYLINE,
|
||||
ArrowAnnotate: POINT,
|
||||
};
|
||||
|
||||
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
|
||||
};
|
||||
|
||||
const _getPointsFromHandles = handles => {
|
||||
let points = [];
|
||||
Object.keys(handles).map(handle => {
|
||||
if (['start', 'end'].includes(handle)) {
|
||||
let point = {};
|
||||
if (handles[handle].x) point.x = handles[handle].x;
|
||||
if (handles[handle].y) point.y = handles[handle].y;
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
return points;
|
||||
};
|
||||
|
||||
const _getHandlesFromPoints = points => {
|
||||
return points
|
||||
.map((p, i) => (i % 10 === 0 ? { start: p } : { end: p }))
|
||||
.reduce((obj, item) => Object.assign(obj, { ...item }), {});
|
||||
};
|
||||
|
||||
return {
|
||||
toAnnotation,
|
||||
toMeasurement,
|
||||
};
|
||||
};
|
||||
|
||||
export default measurementServiceMappingsFactory;
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import measurementServiceMappingsFactory from './measurementServiceMappingsFactory';
|
||||
|
||||
jest.mock('cornerstone-core', () => ({
|
||||
...jest.requireActual('cornerstone-core'),
|
||||
getEnabledElement: () => ({
|
||||
image: { imageId: 123 },
|
||||
}),
|
||||
metaData: {
|
||||
...jest.requireActual('cornerstone-core').metaData,
|
||||
get: () => ({
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('measurementServiceMappings.js', () => {
|
||||
let mappings;
|
||||
let handles;
|
||||
let points;
|
||||
let csToolsAnnotation;
|
||||
let measurement;
|
||||
let measurementServiceMock;
|
||||
let definition = 'Length';
|
||||
|
||||
beforeEach(() => {
|
||||
measurementServiceMock = {
|
||||
VALUE_TYPES: {
|
||||
POLYLINE: 'value_type::polyline',
|
||||
POINT: 'value_type::point',
|
||||
ELLIPSE: 'value_type::ellipse',
|
||||
MULTIPOINT: 'value_type::multipoint',
|
||||
CIRCLE: 'value_type::circle',
|
||||
},
|
||||
};
|
||||
mappings = measurementServiceMappingsFactory(measurementServiceMock);
|
||||
handles = { start: { x: 1, y: 2 }, end: { x: 1, y: 2 } };
|
||||
points = [{ x: 1, y: 2 }, { x: 1, y: 2 }];
|
||||
csToolsAnnotation = {
|
||||
toolName: definition,
|
||||
measurementData: {
|
||||
_measurementServiceId: 1,
|
||||
sopInstanceUid: '123',
|
||||
frameOfReferenceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
handles,
|
||||
text: 'Test',
|
||||
description: 'Test',
|
||||
unit: 'mm',
|
||||
},
|
||||
};
|
||||
measurement = {
|
||||
id: 1,
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '123',
|
||||
referenceSeriesUID: '123',
|
||||
label: 'Test',
|
||||
description: 'Test',
|
||||
unit: 'mm',
|
||||
type: measurementServiceMock.VALUE_TYPES.POLYLINE,
|
||||
points: points,
|
||||
};
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('toAnnotation()', () => {
|
||||
it('map measurement service format to annotation', async () => {
|
||||
const mappedMeasurement = await mappings.toAnnotation(
|
||||
measurement,
|
||||
definition
|
||||
);
|
||||
expect(mappedMeasurement).toEqual(csToolsAnnotation);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toMeasurement()', () => {
|
||||
it('map annotation to measurement service format', async () => {
|
||||
const mappedAnnotation = await mappings.toMeasurement(csToolsAnnotation);
|
||||
expect(mappedAnnotation).toEqual(measurement);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { redux } from '@ohif/core';
|
||||
|
||||
const { setLayout } = redux.actions;
|
||||
|
||||
/**
|
||||
* Update the current layout with a simple Cornerstone one
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
const setCornerstoneLayout = () => {
|
||||
const layout = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewports: [{ plugin: 'cornerstone' }],
|
||||
};
|
||||
|
||||
const action = setLayout(layout);
|
||||
|
||||
window.store.dispatch(action);
|
||||
}
|
||||
|
||||
export default setCornerstoneLayout;
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.1.0...@ohif/extension-dicom-html@1.1.1) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.0.2...@ohif/extension-dicom-html@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -11,26 +11,26 @@ const SOP_CLASS_UIDS = {
|
||||
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
||||
};
|
||||
|
||||
const sopClassUids = Object.values(SOP_CLASS_UIDS);
|
||||
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 OHIFDicomHtmlSopClassHandler = {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
modality: 'SR',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.6...@ohif/extension-dicom-microscopy@0.50.7) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.5...@ohif/extension-dicom-microscopy@0.50.6) (2019-09-26)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "0.50.6",
|
||||
"version": "0.50.7",
|
||||
"description": "OHIF extension for Dicom Microscopy",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -8,7 +8,7 @@ const SOP_CLASS_UIDS = {
|
||||
|
||||
const DicomMicroscopySopClassHandler = {
|
||||
id: 'DicomMicroscopySopClassHandlerPlugin',
|
||||
sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
||||
sopClassUIDs: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
@@ -16,12 +16,12 @@ const DicomMicroscopySopClassHandler = {
|
||||
|
||||
return {
|
||||
plugin: 'microscopy',
|
||||
modality: 'SM',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'SM',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
dicomWebClient,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ class DicomMicroscopyViewport extends Component {
|
||||
const dicomWebClient = displaySet.dicomWebClient;
|
||||
|
||||
const searchInstanceOptions = {
|
||||
studyInstanceUID: displaySet.studyInstanceUid,
|
||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
||||
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||
};
|
||||
|
||||
dicomWebClient
|
||||
@@ -34,18 +34,18 @@ class DicomMicroscopyViewport extends Component {
|
||||
.then(instances => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
const sopInstanceUID = instances[i]['00080018']['Value'][0];
|
||||
const sopInstanceUid = instances[i]['00080018']['Value'][0];
|
||||
const retrieveInstanceOptions = {
|
||||
studyInstanceUID: displaySet.studyInstanceUid,
|
||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
||||
sopInstanceUID,
|
||||
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
};
|
||||
|
||||
const promise = dicomWebClient
|
||||
.retrieveInstanceMetadata(retrieveInstanceOptions)
|
||||
.then(metadata => {
|
||||
const imageType = metadata[0]['00080008']['Value'];
|
||||
if (imageType[2] === 'VOLUME') {
|
||||
const ImageType = metadata[0]['00080008']['Value'];
|
||||
if (ImageType[2] === 'VOLUME') {
|
||||
return metadata[0];
|
||||
}
|
||||
});
|
||||
@@ -56,13 +56,15 @@ class DicomMicroscopyViewport extends Component {
|
||||
.then(async metadata => {
|
||||
metadata = metadata.filter(m => m);
|
||||
|
||||
const { api } = await import(/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer');
|
||||
const { api } = await import(
|
||||
/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'
|
||||
);
|
||||
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
|
||||
|
||||
this.viewer = new microscopyViewer({
|
||||
client: dicomWebClient,
|
||||
metadata,
|
||||
retrieveRendered: false
|
||||
retrieveRendered: false,
|
||||
});
|
||||
|
||||
this.viewer.render({ container });
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.1...@ohif/extension-dicom-pdf@1.0.2) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.0...@ohif/extension-dicom-pdf@1.0.1) (2019-12-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "OHIF extension for Dicom PDF",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -101,9 +101,9 @@ class DicomPDFViewport extends Component {
|
||||
let pdfByteArray = byteArray;
|
||||
|
||||
if (dataSet) {
|
||||
const sopClassUid = dataSet.string('x00080016');
|
||||
const SOPClassUID = dataSet.string('x00080016');
|
||||
|
||||
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||
if (SOPClassUID !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||
throw new Error('This is not a DICOM-encapsulated PDF');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@ const SOP_CLASS_UIDS = {
|
||||
const OHIFDicomPDFSopClassHandler = {
|
||||
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
||||
sopClassUIDs: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'pdf',
|
||||
modality: 'DOC',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'DOC',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders: authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ class OHIFDicomPDFViewport extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { displaySet, studies } = this.props.viewportData;
|
||||
|
||||
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||
data => this.setState({ byteArray: new Uint8Array(data) }),
|
||||
error => {
|
||||
|
||||
@@ -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: 'OHIFExtLesionTracker',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Lesion tracker right panel ([#1428](https://github.com/OHIF/Viewers/issues/1428)) ([98a649b](https://github.com/OHIF/Viewers/commit/98a649b455ffc712938fc5035cdef40695e58440))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 0.1.0 (2020-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* lesion-tracker extension ([#1420](https://github.com/OHIF/Viewers/issues/1420)) ([73e4409](https://github.com/OHIF/Viewers/commit/73e440968ce4699d081a9c9f2d21dd68095b3056))
|
||||
@@ -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,38 @@
|
||||
# @ohif/extension-lesion-tracker
|
||||
|
||||
This project is an OHIF extension that can be used with the Core OHIF Platform,
|
||||
either at runtime, or as an ES6 dependency, to create a medical image viewing
|
||||
application similar to that of the [legacy Lesion
|
||||
Tracker][legacy-lesion-tracker] viewer.
|
||||
|
||||
## About
|
||||
|
||||
...
|
||||
|
||||
## Scope
|
||||
|
||||
...
|
||||
|
||||
### Configuration
|
||||
|
||||
...
|
||||
|
||||
### Extensions
|
||||
|
||||
...
|
||||
|
||||
## Build & Deploy
|
||||
|
||||
...
|
||||
|
||||
## Funding/Support
|
||||
|
||||
...
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[legacy-lesion-tracker]: http://lesiontracker.ohif.org/studylist
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@ohif/extension-lesion-tracker",
|
||||
"version": "0.2.0",
|
||||
"description": "OHIF extension for Lesion Tracker",
|
||||
"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",
|
||||
"dev:lesion-tracker": "yarn run dev",
|
||||
"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": "^0.50.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
.MeasurementComparisonTable {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .displayTexts {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .displayTexts .measurementDisplayText {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .measurementTableHeader {
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .measurementTableHeader .warning-status {
|
||||
left: 0;
|
||||
top: 3px;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .measurementTableHeader .warning-status .warning-border {
|
||||
padding: 2px 3px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .measurementTableHeader .warning-status svg {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .measurementTableHeader .measurementTableHeaderItem {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .generate-report {
|
||||
background-color: #151a1f;
|
||||
margin-top: 2px;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.MeasurementComparisonTable .generate-report button {
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
background-color: var(--active-color);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MeasurementTable } from '@ohif/ui';
|
||||
|
||||
import './MeasurementComparisonTable.css';
|
||||
|
||||
const overallWarnings = {
|
||||
warningList: [
|
||||
'All measurements should have a location',
|
||||
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
|
||||
],
|
||||
};
|
||||
|
||||
const measurements = [
|
||||
{
|
||||
measurementId: '125',
|
||||
measurementNumber: '125',
|
||||
itemNumber: 1,
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '12.5 x 4.6' }],
|
||||
},
|
||||
{
|
||||
measurementId: '124',
|
||||
measurementNumber: '124',
|
||||
itemNumber: 2,
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '32.5 x 1.6' }],
|
||||
},
|
||||
{
|
||||
measurementId: '123',
|
||||
measurementNumber: '123',
|
||||
itemNumber: 3,
|
||||
hasWarnings: true,
|
||||
warningList: [
|
||||
'All measurements should have a location',
|
||||
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
|
||||
],
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '5.5 x 9.2' }],
|
||||
},
|
||||
];
|
||||
|
||||
const additionalFindings = [
|
||||
{
|
||||
measurementId: '122',
|
||||
measurementNumber: '122',
|
||||
itemNumber: 1,
|
||||
hasWarnings: true,
|
||||
warningList: [
|
||||
'All measurements should have a location',
|
||||
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
|
||||
],
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '23.5 x 9.2' }],
|
||||
},
|
||||
{
|
||||
measurementId: '121',
|
||||
measurementNumber: '121',
|
||||
itemNumber: 2,
|
||||
hasWarnings: true,
|
||||
warningList: [
|
||||
'All measurements should have a location',
|
||||
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
|
||||
],
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '11.2 x 9.2' }],
|
||||
},
|
||||
{
|
||||
measurementId: '120',
|
||||
measurementNumber: '120',
|
||||
itemNumber: 3,
|
||||
label: '(No description)',
|
||||
data: [{ displayText: '2.9 x 9.2' }],
|
||||
},
|
||||
];
|
||||
|
||||
const currentCollections = [
|
||||
{
|
||||
selectorAction: () => { },
|
||||
maxMeasurements: 3,
|
||||
groupName: 'Measurements',
|
||||
measurements: measurements,
|
||||
},
|
||||
{
|
||||
selectorAction: () => { },
|
||||
groupName: 'Additional Findings',
|
||||
measurements: additionalFindings,
|
||||
},
|
||||
];
|
||||
|
||||
const comparisonColletions = [
|
||||
{
|
||||
selectorAction: () => { },
|
||||
maxMeasurements: 3,
|
||||
groupName: 'Measurements',
|
||||
measurements: measurements,
|
||||
},
|
||||
{
|
||||
selectorAction: () => { },
|
||||
groupName: 'Additional Findings',
|
||||
measurements: additionalFindings,
|
||||
},
|
||||
];
|
||||
|
||||
const comparisonCollections = currentCollections.map((group, index) => {
|
||||
return {
|
||||
...group,
|
||||
measurements: group.measurements.map((measurement, measurementIndex) => {
|
||||
const comparisonCollection = comparisonColletions[index].measurements;
|
||||
if (measurementIndex < comparisonCollection.length) {
|
||||
return {
|
||||
...measurement,
|
||||
data: [
|
||||
...measurement.data,
|
||||
...comparisonCollection[measurementIndex].data,
|
||||
],
|
||||
};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const comparisonTimepoints = [
|
||||
{
|
||||
key: 'Current',
|
||||
date: '10-Apr-18',
|
||||
},
|
||||
{
|
||||
key: 'Comparison',
|
||||
date: '15-Jun-18',
|
||||
}
|
||||
];
|
||||
|
||||
const MeasurementComparisonTable = () => {
|
||||
return (
|
||||
<div className="MeasurementComparisonTable">
|
||||
<MeasurementTable
|
||||
timepoints={comparisonTimepoints}
|
||||
overallWarnings={overallWarnings}
|
||||
measurementCollection={comparisonCollections}
|
||||
onRelabelClick={() => { }}
|
||||
onEditDescriptionClick={() => { }}
|
||||
/>
|
||||
<div className="generate-report">
|
||||
<button className="btn btn-primary">Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MeasurementComparisonTable;
|
||||
@@ -0,0 +1,52 @@
|
||||
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'lesion-tracker',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'th-list',
|
||||
label: 'Measurements',
|
||||
target: 'lesion-tracker-panel',
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'lesion-tracker-panel',
|
||||
component: MeasurementComparisonTable,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER']
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
* @returns Object
|
||||
*/
|
||||
getToolbarModule() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
* @returns Object
|
||||
*/
|
||||
getCommandsModule() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,188 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.23...@ohif/extension-vtk@1.2.24) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.22...@ohif/extension-vtk@1.2.23) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.21...@ohif/extension-vtk@1.2.22) (2020-03-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.20...@ohif/extension-vtk@1.2.21) (2020-02-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.19...@ohif/extension-vtk@1.2.20) (2020-02-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.18...@ohif/extension-vtk@1.2.19) (2020-02-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.17...@ohif/extension-vtk@1.2.18) (2020-02-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.16...@ohif/extension-vtk@1.2.17) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.15...@ohif/extension-vtk@1.2.16) (2020-02-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.14...@ohif/extension-vtk@1.2.15) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.13...@ohif/extension-vtk@1.2.14) (2020-02-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.12...@ohif/extension-vtk@1.2.13) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.11...@ohif/extension-vtk@1.2.12) (2020-01-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.10...@ohif/extension-vtk@1.2.11) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.9...@ohif/extension-vtk@1.2.10) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.8...@ohif/extension-vtk@1.2.9) (2020-01-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.7...@ohif/extension-vtk@1.2.8) (2020-01-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.5...@ohif/extension-vtk@1.2.6) (2020-01-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.4...@ohif/extension-vtk@1.2.5) (2020-01-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.3...@ohif/extension-vtk@1.2.4) (2019-12-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.2...@ohif/extension-vtk@1.2.3) (2019-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.1...@ohif/extension-vtk@1.2.2) (2019-12-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.24",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -34,7 +34,7 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -49,12 +49,12 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
"react-vtkjs-viewport": "^0.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.3.0",
|
||||
"@ohif/ui": "^1.1.7",
|
||||
"cornerstone-tools": "^4.9.0",
|
||||
"@ohif/core": "^2.5.3",
|
||||
"@ohif/ui": "^1.3.3",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"gh-pages": "^2.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import { View2D } from 'react-vtkjs-viewport';
|
||||
import { connect } from 'react-redux';
|
||||
import VTKViewport from './VTKViewport';
|
||||
|
||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
@@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const pluginDetails = viewportLayout.vtk || {};
|
||||
|
||||
return {
|
||||
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||
layout: state.viewports.layout,
|
||||
isActive,
|
||||
...pluginDetails,
|
||||
@@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps
|
||||
)(View2D);
|
||||
)(VTKViewport);
|
||||
|
||||
export default ConnectedVTKViewport;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
|
||||
|
||||
import ConnectedVTKViewport from './ConnectedVTKViewport';
|
||||
import LoadingIndicator from './LoadingIndicator.js';
|
||||
import OHIF from '@ohif/core';
|
||||
@@ -13,15 +12,7 @@ import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
||||
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
// Metadata configuration
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
@@ -68,15 +59,20 @@ class OHIFVTKViewport extends Component {
|
||||
viewportData: PropTypes.shape({
|
||||
studies: PropTypes.array,
|
||||
displaySet: PropTypes.shape({
|
||||
studyInstanceUid: PropTypes.string,
|
||||
displaySetInstanceUid: PropTypes.string,
|
||||
sopClassUids: PropTypes.arrayOf(PropTypes.string),
|
||||
sopInstanceUid: PropTypes.string,
|
||||
StudyInstanceUID: PropTypes.string,
|
||||
displaySetInstanceUID: PropTypes.string,
|
||||
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
||||
SOPInstanceUID: PropTypes.string,
|
||||
frameIndex: PropTypes.number,
|
||||
}),
|
||||
}),
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onScroll: () => {},
|
||||
};
|
||||
|
||||
static id = 'OHIFVTKViewport';
|
||||
@@ -92,18 +88,18 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
static getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) {
|
||||
// Create shortcut to displaySet
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@@ -114,17 +110,14 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
if (frameIndex !== undefined) {
|
||||
stack.currentImageIdIndex = frameIndex;
|
||||
} else if (sopInstanceUid) {
|
||||
} else if (SOPInstanceUID) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
'sopCommonModule',
|
||||
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
||||
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||
});
|
||||
|
||||
if (index > -1) {
|
||||
@@ -139,31 +132,31 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
getViewportData = (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
let imageDataObject;
|
||||
let labelmapDataObject;
|
||||
|
||||
switch (sopClassUid) {
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
throw new Error('Not yet implemented');
|
||||
/*
|
||||
const data = handleSegmentationStorage(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
imageDataObject = data.referenceDataObject;
|
||||
@@ -177,7 +170,7 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
*/
|
||||
default:
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
|
||||
return imageDataObject;
|
||||
}
|
||||
@@ -189,25 +182,31 @@ class OHIFVTKViewport extends Component {
|
||||
* @param {object} imageDataObject
|
||||
* @param {object} imageDataObject.vtkImageData
|
||||
* @param {object} imageDataObject.imageMetaData0
|
||||
* @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth
|
||||
* @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter
|
||||
* @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc
|
||||
* @param {string} displaySetInstanceUid
|
||||
* @param {number} [imageDataObject.imageMetaData0.WindowWidth] - The volume's initial WindowWidth
|
||||
* @param {number} [imageDataObject.imageMetaData0.WindowCenter] - The volume's initial WindowCenter
|
||||
* @param {string} imageDataObject.imageMetaData0.Modality - CT, MR, PT, etc
|
||||
* @param {string} displaySetInstanceUID
|
||||
* @returns vtkVolumeActor
|
||||
* @memberof OHIFVTKViewport
|
||||
*/
|
||||
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
||||
if (volumeCache[displaySetInstanceUid]) {
|
||||
return volumeCache[displaySetInstanceUid];
|
||||
getOrCreateVolume(imageDataObject, displaySetInstanceUID) {
|
||||
if (volumeCache[displaySetInstanceUID]) {
|
||||
return volumeCache[displaySetInstanceUID];
|
||||
}
|
||||
|
||||
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
||||
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
||||
// TODO -> Should update react-vtkjs-viewport and react-cornerstone-viewports
|
||||
// internals to use naturalized DICOM JSON names.
|
||||
const {
|
||||
windowWidth: WindowWidth,
|
||||
windowCenter: WindowCenter,
|
||||
modality: Modality,
|
||||
} = imageMetaData0;
|
||||
|
||||
const { lower, upper } = _getRangeFromWindowLevels(
|
||||
windowWidth,
|
||||
windowCenter,
|
||||
modality
|
||||
WindowWidth,
|
||||
WindowCenter,
|
||||
Modality
|
||||
);
|
||||
const volumeActor = vtkVolume.newInstance();
|
||||
const volumeMapper = vtkVolumeMapper.newInstance();
|
||||
@@ -231,7 +230,7 @@ class OHIFVTKViewport extends Component {
|
||||
// TODO: maybe we should auto adjust samples to 1000.
|
||||
volumeMapper.setMaximumSamplesPerRay(4000);
|
||||
|
||||
volumeCache[displaySetInstanceUid] = volumeActor;
|
||||
volumeCache[displaySetInstanceUID] = volumeActor;
|
||||
|
||||
return volumeActor;
|
||||
}
|
||||
@@ -239,26 +238,26 @@ class OHIFVTKViewport extends Component {
|
||||
setStateFromProps() {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
sopClassUIDs,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (sopClassUids.length > 1) {
|
||||
if (sopClassUIDs.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
'More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs[0];
|
||||
const imageDataObject = this.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
@@ -272,7 +271,7 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
const volumeActor = this.getOrCreateVolume(
|
||||
imageDataObject,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
this.setState({
|
||||
@@ -312,9 +311,9 @@ class OHIFVTKViewport extends Component {
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUid !==
|
||||
prevDisplaySet.displaySetInstanceUid ||
|
||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
this.setStateFromProps();
|
||||
@@ -326,7 +325,7 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
||||
|
||||
const numberOfFrames = insertPixelDataPromises.length;
|
||||
const NumberOfFrames = insertPixelDataPromises.length;
|
||||
|
||||
if (!isLoading) {
|
||||
this.setState({ isLoaded: true });
|
||||
@@ -336,7 +335,7 @@ class OHIFVTKViewport extends Component {
|
||||
insertPixelDataPromises.forEach(promise => {
|
||||
promise.then(numberProcessed => {
|
||||
const percentComplete = Math.floor(
|
||||
(numberProcessed * 100) / numberOfFrames
|
||||
(numberProcessed * 100) / NumberOfFrames
|
||||
);
|
||||
|
||||
if (percentComplete !== this.state.percentComplete) {
|
||||
@@ -368,8 +367,6 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={style}>
|
||||
@@ -386,6 +383,7 @@ class OHIFVTKViewport extends Component {
|
||||
this.state.paintFilterBackgroundImageData
|
||||
}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
onScroll={this.props.onScroll}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -403,21 +401,21 @@ class OHIFVTKViewport extends Component {
|
||||
* @private
|
||||
* @param {number} [width] - the width of our window
|
||||
* @param {number} [center] - the center of our window
|
||||
* @param {string} [modality] - 'PT', 'CT', etc.
|
||||
* @param {string} [Modality] - 'PT', 'CT', etc.
|
||||
* @returns { lower, upper } - range
|
||||
*/
|
||||
function _getRangeFromWindowLevels(width, center, modality = undefined) {
|
||||
function _getRangeFromWindowLevels(width, center, Modality = undefined) {
|
||||
// For PET just set the range to 0-5 SUV
|
||||
if (Modality === 'PT') {
|
||||
return { lower: 0, upper: 5 };
|
||||
}
|
||||
|
||||
const levelsAreNotNumbers = isNaN(center) || isNaN(width);
|
||||
|
||||
if (levelsAreNotNumbers) {
|
||||
return { lower: 0, upper: 512 };
|
||||
}
|
||||
|
||||
// For PET just set the range to 0-5 SUV
|
||||
if (modality === 'PT') {
|
||||
return { lower: 0, upper: 5 };
|
||||
}
|
||||
|
||||
return {
|
||||
lower: center - width / 2.0,
|
||||
upper: center + width / 2.0,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.vtk-viewport-handler svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { View2D } from 'react-vtkjs-viewport';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './VTKViewport.css';
|
||||
|
||||
const VTKViewport = props => {
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const setViewportActiveHandler = useCallback(() => {
|
||||
const { setViewportActive, viewportIndex, activeViewportIndex } = props;
|
||||
|
||||
if (viewportIndex !== activeViewportIndex) {
|
||||
// set in Connected
|
||||
setViewportActive();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleScrollEvent = evt => {
|
||||
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
||||
const viewportUID = vtkViewportApiReference.uid;
|
||||
const viewportWasScrolled = viewportUID === evt.detail.uid;
|
||||
|
||||
if (viewportWasScrolled) {
|
||||
setViewportActiveHandler();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('vtkscrollevent', handleScrollEvent);
|
||||
return () =>
|
||||
window.removeEventListener('vtkscrollevent', handleScrollEvent);
|
||||
}, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="vtk-viewport-handler"
|
||||
style={style}
|
||||
onClick={setViewportActiveHandler}
|
||||
>
|
||||
<View2D {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
VTKViewport.propTypes = {
|
||||
setViewportActive: PropTypes.func.isRequired,
|
||||
viewportIndex: PropTypes.number.isRequired,
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
/* Receives viewportIndex */
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
VTKViewport.defaultProps = {
|
||||
onScroll: () => {},
|
||||
};
|
||||
|
||||
export default VTKViewport;
|
||||
@@ -70,12 +70,9 @@ const commandsModule = ({ commandsManager }) => {
|
||||
if (cornerstoneElement) {
|
||||
const imageId = cornerstoneElement.image.imageId;
|
||||
|
||||
const { modality } = cornerstone.metaData.get(
|
||||
'generalSeriesModule',
|
||||
imageId
|
||||
);
|
||||
const Modality = cornerstone.metaData.get('Modality', imageId);
|
||||
|
||||
if (modality !== 'PT') {
|
||||
if (Modality !== 'PT') {
|
||||
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
||||
|
||||
return {
|
||||
@@ -103,6 +100,9 @@ const commandsModule = ({ commandsManager }) => {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getVtkApis: ({ index }) => {
|
||||
return apis[index];
|
||||
},
|
||||
axial: async ({ viewports }) => {
|
||||
const api = await _getActiveViewportVTKApi(viewports);
|
||||
|
||||
@@ -234,7 +234,7 @@ const commandsModule = ({ commandsManager }) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
// Sagital
|
||||
// Sagittal
|
||||
orientation: {
|
||||
sliceNormal: [1, 0, 0],
|
||||
viewUp: [0, 0, 1],
|
||||
@@ -266,11 +266,12 @@ const commandsModule = ({ commandsManager }) => {
|
||||
'crosshairsWidget'
|
||||
);
|
||||
|
||||
const uid = api.uid;
|
||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
configuration: { apis, apiIndex },
|
||||
configuration: { apis, apiIndex, uid },
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -296,55 +297,45 @@ const commandsModule = ({ commandsManager }) => {
|
||||
},
|
||||
enableRotateTool: {
|
||||
commandFn: actions.enableRotateTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
enableCrosshairsTool: {
|
||||
commandFn: actions.enableCrosshairsTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
enableLevelTool: {
|
||||
commandFn: actions.enableLevelTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
setBlendModeToComposite: {
|
||||
commandFn: actions.setBlendModeToComposite,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
||||
},
|
||||
setBlendModeToMaximumIntensity: {
|
||||
commandFn: actions.setBlendModeToMaximumIntensity,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
||||
},
|
||||
setBlendModeToMinimumIntensity: {
|
||||
commandFn: actions.setBlendMode,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
||||
},
|
||||
setBlendModeToAverageIntensity: {
|
||||
commandFn: actions.setBlendMode,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
||||
},
|
||||
setSlabThickness: {
|
||||
// TODO: How do we pass in a function argument?
|
||||
commandFn: actions.setSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
increaseSlabThickness: {
|
||||
commandFn: actions.changeSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {
|
||||
change: 3,
|
||||
},
|
||||
},
|
||||
decreaseSlabThickness: {
|
||||
commandFn: actions.changeSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {
|
||||
change: -3,
|
||||
},
|
||||
@@ -355,6 +346,10 @@ const commandsModule = ({ commandsManager }) => {
|
||||
options: {},
|
||||
context: 'VIEWER',
|
||||
},
|
||||
getVtkApiForViewportIndex: {
|
||||
commandFn: actions.getVtkApis,
|
||||
context: 'VIEWER',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import * as dcmjs from "dcmjs";
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import OHIF from "@ohif/core";
|
||||
import { api } from "dicomweb-client";
|
||||
import OHIF from '@ohif/core';
|
||||
import { api } from 'dicomweb-client';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.seriesInstanceUid === seriesInstanceUid;
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
// Create shortcut to displaySet
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@@ -52,19 +52,19 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function retrieveDicomData(
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUID,
|
||||
sopInstanceUid,
|
||||
wadoRoot
|
||||
) {
|
||||
const config = {
|
||||
url: wadoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader()
|
||||
headers: DICOMWeb.getAuthorizationHeader(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUID
|
||||
sopInstanceUid,
|
||||
};
|
||||
|
||||
return dicomWeb.retrieveInstance(options);
|
||||
@@ -72,30 +72,26 @@ function retrieveDicomData(
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
|
||||
// from google cloud
|
||||
const wadoRoot = displaySet.images[0].getData().wadoRoot;
|
||||
|
||||
const StudyInstanceUID = displaySet.images[0].getStudyInstanceUID();
|
||||
const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID();
|
||||
const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID();
|
||||
|
||||
const arrayBuffer = await retrieveDicomData(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
displaySet.images[0].getStudyInstanceUID(),
|
||||
displaySet.images[0].getSeriesInstanceUID(),
|
||||
displaySet.images[0].getSOPInstanceUID(),
|
||||
wadoRoot
|
||||
);
|
||||
|
||||
@@ -108,16 +104,16 @@ async function handleSegmentationStorage(
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
"More than one display set with the same seriesInstanceUid. This is not supported yet..."
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ async function handleSegmentationStorage(
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
|
||||
|
||||
if (!results) {
|
||||
throw new Error("Fractional segmentations are not supported");
|
||||
throw new Error('Fractional segmentations are not supported');
|
||||
}
|
||||
|
||||
const cachedStack = StackManager.findOrCreateStack(
|
||||
@@ -137,13 +133,13 @@ async function handleSegmentationStorage(
|
||||
|
||||
return {
|
||||
referenceDataObject,
|
||||
labelmapDataObject
|
||||
labelmapDataObject,
|
||||
};
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import withCommandsManager from './withCommandsManager.js';
|
||||
// This feels weird
|
||||
// import loadLocales from './loadLocales';
|
||||
|
||||
@@ -14,8 +15,8 @@ const vtkExtension = {
|
||||
*/
|
||||
id: 'vtk',
|
||||
|
||||
getViewportModule() {
|
||||
return OHIFVTKViewport;
|
||||
getViewportModule({ commandsManager }) {
|
||||
return withCommandsManager(OHIFVTKViewport, commandsManager);
|
||||
},
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ToolbarButton } from '@ohif/ui';
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
let isVisible = true;
|
||||
|
||||
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
|
||||
if (!viewportSpecificData[activeViewportIndex]) {
|
||||
return false;
|
||||
};
|
||||
|
||||
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
|
||||
activeViewportIndex
|
||||
];
|
||||
|
||||
const studies = studyMetadataManager.all();
|
||||
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
|
||||
|
||||
if (!displaySet) {
|
||||
return false;
|
||||
};
|
||||
|
||||
return displaySet.isReconstructable;
|
||||
};
|
||||
|
||||
function VTKMPRToolbarButton({
|
||||
parentContext,
|
||||
toolbarClickCallback,
|
||||
button,
|
||||
activeButtons,
|
||||
isActive,
|
||||
className,
|
||||
}) {
|
||||
const { id, label, icon } = button;
|
||||
const { viewportSpecificData, activeViewportIndex } = useSelector(state => {
|
||||
const { viewports = {} } = state;
|
||||
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||
|
||||
return {
|
||||
viewportSpecificData,
|
||||
activeViewportIndex,
|
||||
}
|
||||
});
|
||||
|
||||
isVisible = _isDisplaySetReconstructable(
|
||||
viewportSpecificData,
|
||||
activeViewportIndex,
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isVisible && (
|
||||
<ToolbarButton
|
||||
key={id}
|
||||
label={label}
|
||||
icon={icon}
|
||||
onClick={evt => toolbarClickCallback(button, evt)}
|
||||
isActive={isActive}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
VTKMPRToolbarButton.propTypes = {
|
||||
parentContext: PropTypes.object.isRequired,
|
||||
toolbarClickCallback: PropTypes.func.isRequired,
|
||||
button: PropTypes.object.isRequired,
|
||||
activeButtons: PropTypes.array.isRequired,
|
||||
isActive: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default VTKMPRToolbarButton;
|
||||
@@ -1,4 +1,5 @@
|
||||
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
|
||||
import VTKMPRToolbarButton from './toolbarComponents/VTKMPRToolbarButton';
|
||||
|
||||
const TOOLBAR_BUTTON_TYPES = {
|
||||
COMMAND: 'command',
|
||||
@@ -115,9 +116,19 @@ const definitions = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2DMPR',
|
||||
label: '2D MPR',
|
||||
icon: 'cube',
|
||||
//
|
||||
CustomComponent: VTKMPRToolbarButton,
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'mpr2d',
|
||||
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function withCommandsManager(Component, commandsManager = {}) {
|
||||
return class WithCommandsManager extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Component
|
||||
{...this.props}
|
||||
onScroll={viewportIndex =>
|
||||
commandsManager.runCommand('getVtkApiForViewportIndex', {
|
||||
index: viewportIndex,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+2
-1
@@ -10,7 +10,8 @@ module.exports = {
|
||||
// https://jestjs.io/docs/en/configuration#projects-array-string-projectconfig
|
||||
projects: [
|
||||
// "<rootDir>/extensions/*/jest.config.js",
|
||||
"<rootDir>/platform/*/jest.config.js"
|
||||
"<rootDir>/platform/*/jest.config.js",
|
||||
"<rootDir>/extensions/*/jest.config.js"
|
||||
],
|
||||
coverageDirectory: "<rootDir>/coverage/"
|
||||
};
|
||||
+3
-1
@@ -13,11 +13,13 @@
|
||||
"scripts": {
|
||||
"cm": "npx git-cz",
|
||||
"build": "lerna run build:viewer --stream",
|
||||
"build:lt": "lerna run build:viewer:lesion-tracker --stream",
|
||||
"build:ci": "lerna run build:viewer:ci --stream",
|
||||
"build:demo": "lerna run build:viewer:demo --stream",
|
||||
"build:package": "lerna run build:viewer:package --stream",
|
||||
"build:package-all": "lerna run build:package --parallel --stream",
|
||||
"dev": "lerna run dev:viewer --stream",
|
||||
"dev:lt": "lerna run dev:viewer:lesion-tracker --stream",
|
||||
"dev:project": ".scripts/dev.sh",
|
||||
"dev:orthanc": "lerna run dev:orthanc --stream",
|
||||
"orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up",
|
||||
@@ -59,6 +61,7 @@
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.2.0",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"dotenv": "^8.1.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-prettier": "^6.4.0",
|
||||
@@ -72,7 +75,6 @@
|
||||
"eslint-plugin-react": "7.x",
|
||||
"eslint-plugin-react-hooks": "1.5.0",
|
||||
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
||||
"favicons-webpack-plugin": "^1.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^3.0.0",
|
||||
"jest": "^24.8.0",
|
||||
|
||||
@@ -3,6 +3,147 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.5.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.5.2...@ohif/core@2.5.3) (2020-03-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.5.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.5.1...@ohif/core@2.5.2) (2020-03-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.5.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.5.0...@ohif/core@2.5.1) (2020-02-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 💡 Update log to warn ([#1454](https://github.com/OHIF/Viewers/issues/1454)) ([509a7b7](https://github.com/OHIF/Viewers/commit/509a7b7e57834a0653add98cc320b2a5dbefd51d)), closes [#1451](https://github.com/OHIF/Viewers/issues/1451)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.5.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.4.1...@ohif/core@2.5.0) (2020-02-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* [#1342](https://github.com/OHIF/Viewers/issues/1342) - Window level tab ([#1429](https://github.com/OHIF/Viewers/issues/1429)) ([ebc01a8](https://github.com/OHIF/Viewers/commit/ebc01a8ca238d5a3437b44d81f75aa8a5e8d0574))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.4.0...@ohif/core@2.4.1) (2020-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Combined Hotkeys for special characters ([#1233](https://github.com/OHIF/Viewers/issues/1233)) ([2f30e7a](https://github.com/OHIF/Viewers/commit/2f30e7a821a238144c49c56f37d8e5565540b4bd))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.9...@ohif/core@2.4.0) (2020-02-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 MeasurementService ([#1314](https://github.com/OHIF/Viewers/issues/1314)) ([0c37a40](https://github.com/OHIF/Viewers/commit/0c37a406d963569af8c3be24c697dafd42712dfc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.8...@ohif/core@2.3.9) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.7...@ohif/core@2.3.8) (2020-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove trash data from redux storage after updates ([#1358](https://github.com/OHIF/Viewers/issues/1358)) ([7b2d44f](https://github.com/OHIF/Viewers/commit/7b2d44f2c18241ea521b8d3652aee32e36eaddb8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.6...@ohif/core@2.3.7) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.5...@ohif/core@2.3.6) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.4...@ohif/core@2.3.5) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.3...@ohif/core@2.3.4) (2020-01-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.2...@ohif/core@2.3.3) (2020-01-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.0...@ohif/core@2.3.1) (2019-12-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.2.1...@ohif/core@2.3.0) (2019-12-20)
|
||||
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ import { studies } from '@ohif/core';
|
||||
|
||||
const studiesMetadata = await studies.retrieveStudiesMetadata(
|
||||
server, // Object
|
||||
studyInstanceUids, // Array
|
||||
seriesInstanceUids // Array (optional)
|
||||
studyInstanceUIDs, // Array
|
||||
seriesInstanceUIDs // Array (optional)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.3.0",
|
||||
"version": "2.5.3",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.9.0",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@ const retrieveMeasurements = server => {
|
||||
|
||||
const serverUrl = server.wadoRoot;
|
||||
const studies = utils.studyMetadataManager.all();
|
||||
|
||||
const latestSeries = findMostRecentStructuredReport(studies);
|
||||
|
||||
if (!latestSeries) return Promise.resolve({});
|
||||
@@ -57,17 +58,17 @@ const storeMeasurements = async (measurementData, filter, server) => {
|
||||
const serverUrl = server.wadoRoot;
|
||||
const firstMeasurementKey = Object.keys(measurementData)[0];
|
||||
const firstMeasurement = measurementData[firstMeasurementKey][0];
|
||||
const studyInstanceUid =
|
||||
firstMeasurement && firstMeasurement.studyInstanceUid;
|
||||
const StudyInstanceUID =
|
||||
firstMeasurement && firstMeasurement.StudyInstanceUID;
|
||||
|
||||
try {
|
||||
await stowSRFromMeasurements(measurementData, serverUrl);
|
||||
if (studyInstanceUid) {
|
||||
studies.deleteStudyMetadataPromise(studyInstanceUid);
|
||||
if (StudyInstanceUID) {
|
||||
studies.deleteStudyMetadataPromise(StudyInstanceUID);
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Measurements were saved with success',
|
||||
message: 'Measurements saved successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid';
|
||||
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
|
||||
|
||||
/**
|
||||
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
|
||||
@@ -13,6 +13,7 @@ import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBy
|
||||
*/
|
||||
const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
// Get the dicom data as an Object
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
@@ -30,17 +31,18 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
measurementData[toolName] = [];
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
const instanceMetadata = findInstanceMetadataBySopInstanceUid(
|
||||
const instanceMetadata = findInstanceMetadataBySopInstanceUID(
|
||||
displaySets,
|
||||
measurement.sopInstanceUid
|
||||
);
|
||||
|
||||
const { _study: study, _series: series } = instanceMetadata;
|
||||
const { studyInstanceUid, patientId } = study;
|
||||
const { seriesInstanceUid } = series;
|
||||
const { StudyInstanceUID, PatientID } = study;
|
||||
const { SeriesInstanceUID } = series;
|
||||
const { sopInstanceUid, frameIndex } = measurement;
|
||||
const imagePath = getImagePath(
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
@@ -56,9 +58,10 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
const toolData = Object.assign({}, measurement, {
|
||||
imageId,
|
||||
imagePath,
|
||||
seriesInstanceUid,
|
||||
studyInstanceUid,
|
||||
patientId,
|
||||
SOPInstanceUID: sopInstanceUid,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
PatientID,
|
||||
measurementNumber: ++measurementNumber,
|
||||
timepointId: currentTimepointId,
|
||||
toolType: toolName,
|
||||
@@ -75,19 +78,19 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
/**
|
||||
* Function to create imagePath with all imageData related
|
||||
*
|
||||
* @param {string} studyInstanceUid
|
||||
* @param {string} seriesInstanceUid
|
||||
* @param {string} sopInstanceUid
|
||||
* @param {string} StudyInstanceUID
|
||||
* @param {string} SeriesInstanceUID
|
||||
* @param {string} SOPInstanceUID
|
||||
* @param {string} frameIndex
|
||||
* @returns
|
||||
*/
|
||||
const getImagePath = (
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join(
|
||||
return [StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, frameIndex].join(
|
||||
'_'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
* Should Find the requested instance metadata into the displaySets and return
|
||||
*
|
||||
* @param {Array} displaySets - List of displaySets
|
||||
* @param {string} sopInstanceUid - sopInstanceUID to look for
|
||||
* @param {string} SOPInstanceUID - sopInstanceUID to look for
|
||||
* @returns {Object} - instance metadata found
|
||||
*/
|
||||
const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
||||
const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => {
|
||||
let instanceFound;
|
||||
|
||||
displaySets.find(displaySet => {
|
||||
if (!displaySet.images) return false;
|
||||
|
||||
instanceFound = displaySet.images.find(
|
||||
instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid
|
||||
instanceMetadata =>
|
||||
instanceMetadata.getSOPInstanceUID() === SOPInstanceUID
|
||||
);
|
||||
|
||||
return !!instanceFound;
|
||||
@@ -21,4 +22,4 @@ const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
||||
return instanceFound;
|
||||
};
|
||||
|
||||
export default findInstanceMetadataBySopInstanceUid;
|
||||
export default findInstanceMetadataBySopInstanceUID;
|
||||
@@ -37,9 +37,9 @@ const isStructuredReportSeries = series => {
|
||||
];
|
||||
|
||||
const firstInstance = series.getFirstInstance();
|
||||
const sopClassUid = firstInstance._instance.sopClassUid;
|
||||
const SOPClassUID = firstInstance.getData().metadata.SOPClassUID;
|
||||
|
||||
return supportedSopClassUIDs.includes(sopClassUid);
|
||||
return supportedSopClassUIDs.includes(SOPClassUID);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,9 +51,9 @@ const isStructuredReportSeries = series => {
|
||||
*/
|
||||
const compareSeriesDate = (series1, series2) => {
|
||||
return (
|
||||
series1._data.seriesDate > series2._data.seriesDate ||
|
||||
(series1._data.seriesDate === series2._data.seriesDate &&
|
||||
series1._data.seriesTime > series2._data.seriesTime)
|
||||
series1._data.SeriesDate > series2._data.SeriesDate ||
|
||||
(series1._data.SeriesDate === series2._data.SeriesDate &&
|
||||
series1._data.SeriesTime > series2._data.SeriesTime)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
export default function getModalities(modality, modalitiesInStudy) {
|
||||
if (!modality && !modalitiesInStudy) {
|
||||
export default function getModalities(Modality, ModalitiesInStudy) {
|
||||
if (!Modality && !ModalitiesInStudy) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const modalities = modality || {
|
||||
const modalities = Modality || {
|
||||
vr: 'CS',
|
||||
Value: [],
|
||||
};
|
||||
|
||||
if (modalitiesInStudy) {
|
||||
if (modalities.vr && modalities.vr === modalitiesInStudy.vr) {
|
||||
for (let i = 0; i < modalitiesInStudy.Value.length; i++) {
|
||||
const value = modalitiesInStudy.Value[i];
|
||||
if (ModalitiesInStudy) {
|
||||
if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {
|
||||
for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {
|
||||
const value = ModalitiesInStudy.Value[i];
|
||||
if (modalities.Value.indexOf(value) === -1) {
|
||||
modalities.Value.push(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return modalitiesInStudy;
|
||||
return ModalitiesInStudy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
import getModalities from './getModalities';
|
||||
|
||||
describe('getModalities', () => {
|
||||
test('should return an empty object when modality and modalitiesInStudy are not present', () => {
|
||||
const modality = null;
|
||||
const modalitiesInStudy = null;
|
||||
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||
const Modality = null;
|
||||
const ModalitiesInStudy = null;
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual({});
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual({});
|
||||
});
|
||||
|
||||
test('should return an empty object when modality and modalitiesInStudy are not present', () => {
|
||||
const modality = null;
|
||||
const modalitiesInStudy = null;
|
||||
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||
const Modality = null;
|
||||
const ModalitiesInStudy = null;
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual({});
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual({});
|
||||
});
|
||||
|
||||
test('should return modalities in Study when modality is not defined', () => {
|
||||
const modality = null;
|
||||
test('should return modalities in Study when Modality is not defined', () => {
|
||||
const Modality = null;
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['MOCKED_VALUE'],
|
||||
vr: 'MOCKED_VALUE',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
||||
modalitiesInStudy
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||
ModalitiesInStudy
|
||||
);
|
||||
});
|
||||
|
||||
test('should return only the modalitues that exists in modalitiesInStudy', () => {
|
||||
const modality = {
|
||||
test('should return only the modalitues that exists in ModalitiesInStudy', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['DESIRED_VALUE', 'NOT_DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(modality);
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality);
|
||||
});
|
||||
|
||||
test('should return the seek modality when the desired modality does not exist in modalitiesInStudy', () => {
|
||||
const modality = {
|
||||
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['NOT_DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(modality);
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality);
|
||||
});
|
||||
|
||||
test('should return the seek modality when the desired modality does not exist in modalitiesInStudy VR', () => {
|
||||
const modality = {
|
||||
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy VR', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['NOT_DESIRED_VALUE'],
|
||||
vr: 'ANOTHER_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
||||
modalitiesInStudy
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||
ModalitiesInStudy
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
};
|
||||
@@ -161,7 +161,7 @@ export class CommandsManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const { commandFn, storeContexts } = definition;
|
||||
const { commandFn, storeContexts = [] } = definition;
|
||||
const definitionOptions = definition.options;
|
||||
|
||||
let commandParams = {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import hotkeys from './hotkeys';
|
||||
import hotkeys from './../utils/hotkeys';
|
||||
import log from './../log.js';
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,7 @@ import log from './../log.js';
|
||||
*/
|
||||
|
||||
export class HotkeysManager {
|
||||
constructor(commandsManager) {
|
||||
constructor(commandsManager, servicesManager) {
|
||||
this.hotkeyDefinitions = {};
|
||||
this.hotkeyDefaults = [];
|
||||
this.isEnabled = true;
|
||||
@@ -22,9 +22,19 @@ export class HotkeysManager {
|
||||
);
|
||||
}
|
||||
|
||||
this._servicesManager = servicesManager;
|
||||
this._commandsManager = commandsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes Mousetrap.js's `.record` method, added by the record plugin.
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
record(event) {
|
||||
return hotkeys.record(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables all hotkeys. Hotkeys added while disabled will not listen for
|
||||
* input.
|
||||
@@ -45,38 +55,57 @@ export class HotkeysManager {
|
||||
/**
|
||||
* Registers a list of hotkeydefinitions.
|
||||
*
|
||||
* @param {HotkeyDefinition[] | Object} hotkeyDefinitions Contains hotkeys definitions
|
||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||
*/
|
||||
setHotkeys(hotkeyDefinitions) {
|
||||
const definitions = Array.isArray(hotkeyDefinitions)
|
||||
? [...hotkeyDefinitions]
|
||||
: this._parseToArrayLike(hotkeyDefinitions);
|
||||
setHotkeys(hotkeyDefinitions = []) {
|
||||
try {
|
||||
const definitions = this._getValidDefinitions(hotkeyDefinitions);
|
||||
|
||||
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||
} catch (error) {
|
||||
const { UINotificationService } = this._servicesManager.services;
|
||||
UINotificationService.show({
|
||||
title: 'Hotkeys Manager',
|
||||
message: 'Erro while setting hotkeys',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default hotkey bindings. These
|
||||
* values are used in `this.restoreDefaultBindings`.
|
||||
*
|
||||
* @param {HotkeyDefinition[] | Object} hotkeyDefinitions Contains hotkeys definitions
|
||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||
*/
|
||||
setDefaultHotKeys(hotkeyDefinitions) {
|
||||
setDefaultHotKeys(hotkeyDefinitions = []) {
|
||||
const definitions = this._getValidDefinitions(hotkeyDefinitions);
|
||||
|
||||
this.hotkeyDefaults = definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take hotkey definitions that can be an array or object and make sure that it
|
||||
* returns an array of hotkeys
|
||||
*
|
||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||
*/
|
||||
_getValidDefinitions(hotkeyDefinitions) {
|
||||
const definitions = Array.isArray(hotkeyDefinitions)
|
||||
? [...hotkeyDefinitions]
|
||||
: this._parseToArrayLike(hotkeyDefinitions);
|
||||
|
||||
this.hotkeyDefaults = definitions;
|
||||
return definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* It parses given object containing hotkeyDefinition to array like.
|
||||
* Each property of given object will be mapped to an object of an array. And its property name will be the value of a property named as commandName
|
||||
*
|
||||
* @param {HotkeyDefinition[] | Object} hotkeyDefinitions Contains hotkeys definitions
|
||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions={}] Contains hotkeys definitions
|
||||
* @returns {HotkeyDefinition[]}
|
||||
*/
|
||||
_parseToArrayLike(hotkeyDefinitionsObj) {
|
||||
_parseToArrayLike(hotkeyDefinitionsObj = {}) {
|
||||
const copy = { ...hotkeyDefinitionsObj };
|
||||
return Object.entries(copy).map(entryValue =>
|
||||
this._parseToHotKeyObj(entryValue[0], entryValue[1])
|
||||
@@ -127,11 +156,13 @@ export class HotkeysManager {
|
||||
if (previouslyRegisteredDefinition) {
|
||||
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
||||
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
||||
log.info(`Unbinding ${commandName} from ${previouslyRegisteredKeys}`);
|
||||
}
|
||||
|
||||
// Set definition & bind
|
||||
this.hotkeyDefinitions[commandName] = { keys, label };
|
||||
this._bindHotkeys(commandName, keys);
|
||||
log.info(`Binding ${commandName} to ${keys}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,12 +198,11 @@ export class HotkeysManager {
|
||||
}
|
||||
|
||||
const isKeyArray = keys instanceof Array;
|
||||
if (isKeyArray) {
|
||||
keys.forEach(key => this._bindHotkeys(commandName, key));
|
||||
return;
|
||||
}
|
||||
const combinedKeys = isKeyArray ? keys.join('+') : keys;
|
||||
|
||||
hotkeys.bind(keys, evt => {
|
||||
hotkeys.bind(combinedKeys, evt => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this._commandsManager.runCommand(commandName, { evt });
|
||||
});
|
||||
}
|
||||
@@ -193,7 +223,8 @@ export class HotkeysManager {
|
||||
|
||||
const isKeyArray = keys instanceof Array;
|
||||
if (isKeyArray) {
|
||||
keys.forEach(key => this._unbindHotkeys(commandName, key));
|
||||
const combinedKeys = keys.join('+');
|
||||
this._unbindHotkeys(commandName, combinedKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import CommandsManager from './CommandsManager.js';
|
||||
import HotkeysManager from './HotkeysManager.js';
|
||||
import hotkeys from './hotkeys';
|
||||
import hotkeys from './../utils/hotkeys';
|
||||
import log from './../log.js';
|
||||
|
||||
jest.mock('./CommandsManager.js');
|
||||
jest.mock('./hotkeys');
|
||||
jest.mock('./../utils/hotkeys');
|
||||
jest.mock('./../log.js');
|
||||
|
||||
describe('HotkeysManager', () => {
|
||||
@@ -60,7 +60,10 @@ describe('HotkeysManager', () => {
|
||||
});
|
||||
|
||||
describe('enable()', () => {
|
||||
beforeEach(() => hotkeys.unpause.mockClear());
|
||||
beforeEach(() => {
|
||||
hotkeys.unpause = jest.fn();
|
||||
hotkeys.unpause.mockClear();
|
||||
});
|
||||
|
||||
it('sets isEnabled property to true', () => {
|
||||
hotkeysManager.disable();
|
||||
@@ -139,20 +142,18 @@ describe('HotkeysManager', () => {
|
||||
expectedHotkeyDefinition
|
||||
);
|
||||
});
|
||||
it('calls hotkeys.bind for all keys in array', () => {
|
||||
const definition = { commandName: 'dance', keys: ['h', 'e', 'l', 'o'] };
|
||||
it('calls hotkeys.bind for the group of keys', () => {
|
||||
const definition = { commandName: 'dance', keys: ['shift', 'e'] };
|
||||
|
||||
hotkeysManager.registerHotkeys(definition);
|
||||
|
||||
expect(hotkeys.bind.mock.calls.length).toBe(definition.keys.length);
|
||||
definition.keys.forEach((key, i) =>
|
||||
expect(hotkeys.bind.mock.calls[i][0]).toBe(key)
|
||||
);
|
||||
expect(hotkeys.bind.mock.calls.length).toBe(1);
|
||||
expect(hotkeys.bind.mock.calls[0][0]).toBe('shift+e');
|
||||
});
|
||||
it('calls hotkeys.unbind if commandName was previously registered, for each previously registered set of keys', () => {
|
||||
const firstDefinition = {
|
||||
commandName: 'dance',
|
||||
keys: ['h', 'e', 'l', 'o'],
|
||||
keys: ['alt', 'e'],
|
||||
};
|
||||
const secondDefinition = { commandName: 'dance', keys: 'a' };
|
||||
|
||||
@@ -161,12 +162,8 @@ describe('HotkeysManager', () => {
|
||||
// Second call
|
||||
hotkeysManager.registerHotkeys(secondDefinition);
|
||||
|
||||
expect(hotkeys.unbind.mock.calls.length).toBe(
|
||||
firstDefinition.keys.length
|
||||
);
|
||||
firstDefinition.keys.forEach((key, i) =>
|
||||
expect(hotkeys.unbind.mock.calls[i][0]).toBe(key)
|
||||
);
|
||||
expect(hotkeys.unbind.mock.calls.length).toBe(1);
|
||||
expect(hotkeys.unbind.mock.calls[0][0]).toBe('alt+e');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,412 +1,508 @@
|
||||
import { parsingUtils } from '../lib/parsingUtils';
|
||||
|
||||
const FUNCTION = 'function';
|
||||
import * as dcmjs from 'dcmjs';
|
||||
import queryString from 'query-string';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import { getFallbackTagFromInstance } from '../utils/metadataProvider/metadataProviderFallbackTags';
|
||||
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
||||
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
|
||||
|
||||
class MetadataProvider {
|
||||
constructor() {
|
||||
// Define the main "metadataLookup" private property as an immutable property.
|
||||
Object.defineProperty(this, 'metadataLookup', {
|
||||
Object.defineProperty(this, 'studies', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: new Map(),
|
||||
});
|
||||
|
||||
// Local reference to provider function bound to current instance.
|
||||
Object.defineProperty(this, '_provider', {
|
||||
Object.defineProperty(this, 'imageIdToUIDs', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null,
|
||||
writable: false,
|
||||
value: new Map(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cornerstone Metadata provider to store image meta data
|
||||
* Data from instances, series, and studies are associated with
|
||||
* imageIds to facilitate usage of this information by Cornerstone's Tools
|
||||
*
|
||||
* e.g. the imagePlane metadata object contains instance information about
|
||||
* row/column pixel spacing, patient position, and patient orientation. It
|
||||
* is used in CornerstoneTools to position reference lines and orientation markers.
|
||||
*
|
||||
* @param {String} imageId The Cornerstone ImageId
|
||||
* @param {Object} data An object containing instance, series, and study metadata
|
||||
*/
|
||||
addMetadata(imageId, data) {
|
||||
const instanceMetadata = data.instance;
|
||||
const seriesMetadata = data.series;
|
||||
const studyMetadata = data.study;
|
||||
const numImages = data.numImages;
|
||||
const metadata = {};
|
||||
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
|
||||
let dicomJSONDataset;
|
||||
|
||||
metadata.frameNumber = data.frameNumber;
|
||||
// If Arraybuffer, parse to DICOMJSON before naturalizing.
|
||||
if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {
|
||||
const dicomData = DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer);
|
||||
|
||||
metadata.study = {
|
||||
accessionNumber: studyMetadata.accessionNumber,
|
||||
patientId: studyMetadata.patientId,
|
||||
studyInstanceUid: studyMetadata.studyInstanceUid,
|
||||
studyDate: studyMetadata.studyDate,
|
||||
studyTime: studyMetadata.studyTime,
|
||||
studyDescription: studyMetadata.studyDescription,
|
||||
institutionName: studyMetadata.institutionName,
|
||||
patientHistory: studyMetadata.patientHistory,
|
||||
};
|
||||
|
||||
metadata.series = {
|
||||
seriesDescription: seriesMetadata.seriesDescription,
|
||||
seriesNumber: seriesMetadata.seriesNumber,
|
||||
seriesDate: seriesMetadata.seriesDate,
|
||||
seriesTime: seriesMetadata.seriesTime,
|
||||
modality: seriesMetadata.modality,
|
||||
seriesInstanceUid: seriesMetadata.seriesInstanceUid,
|
||||
numImages: numImages,
|
||||
};
|
||||
|
||||
metadata.instance = instanceMetadata;
|
||||
|
||||
metadata.patient = {
|
||||
name: studyMetadata.patientName,
|
||||
id: studyMetadata.patientId,
|
||||
birthDate: studyMetadata.patientBirthDate,
|
||||
sex: studyMetadata.patientSex,
|
||||
age: studyMetadata.patientAge,
|
||||
};
|
||||
|
||||
// If there is sufficient information, populate
|
||||
// the imagePlane object for easier use in the Viewer
|
||||
metadata.imagePlane = this.getImagePlane(instanceMetadata);
|
||||
|
||||
// Add the metadata to the imageId lookup object
|
||||
this.metadataLookup.set(imageId, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the metadata for the given imageId
|
||||
* @param {String} imageId The Cornerstone ImageId
|
||||
* @returns image metadata
|
||||
*/
|
||||
getMetadata(imageId) {
|
||||
return this.metadataLookup.get(imageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of metadata to the Cornerstone metadata provider given a specific
|
||||
* imageId, type, and dataset
|
||||
*
|
||||
* @param imageId
|
||||
* @param type (e.g. series, instance, tagDisplay)
|
||||
* @param data
|
||||
*/
|
||||
addSpecificMetadata(imageId, type, data) {
|
||||
const metadata = {};
|
||||
metadata[type] = data;
|
||||
|
||||
const oldMetadata = this.metadataLookup.get(imageId);
|
||||
this.metadataLookup.set(imageId, Object.assign(oldMetadata, metadata));
|
||||
}
|
||||
|
||||
getFromImage(image, type, tag, attrName, defaultValue) {
|
||||
let value;
|
||||
|
||||
if (image.data) {
|
||||
value = this.getFromDataSet(image.data, type, tag);
|
||||
dicomJSONDataset = dicomData.dict;
|
||||
} else {
|
||||
value = image.instance[attrName];
|
||||
dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;
|
||||
}
|
||||
|
||||
return value === null ? defaultValue : value;
|
||||
}
|
||||
// Check if dataset is already naturalized.
|
||||
|
||||
getFromDataSet(dataSet, type, tag) {
|
||||
if (!dataSet) {
|
||||
return;
|
||||
}
|
||||
let naturalizedDataset;
|
||||
|
||||
const fn = dataSet[type];
|
||||
if (!fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn.call(dataSet, tag);
|
||||
}
|
||||
|
||||
getFrameIncrementPointer(image) {
|
||||
const dataSet = image.data;
|
||||
let frameInstancePointer = '';
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
const frameInstancePointerNames = {
|
||||
x00181063: 'frameTime',
|
||||
x00181065: 'frameTimeVector',
|
||||
};
|
||||
|
||||
// (0028,0009) = Frame Increment Pointer
|
||||
const frameInstancePointerTag = parsingUtils.attributeTag(
|
||||
dataSet,
|
||||
'x00280009'
|
||||
if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {
|
||||
naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomJSONDataset
|
||||
);
|
||||
frameInstancePointer = frameInstancePointerNames[frameInstancePointerTag];
|
||||
} else {
|
||||
frameInstancePointer = image.instance.frameIncrementPointer;
|
||||
naturalizedDataset = dicomJSONDataset;
|
||||
}
|
||||
|
||||
return frameInstancePointer || '';
|
||||
}
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = naturalizedDataset;
|
||||
|
||||
getFrameTimeVector(image) {
|
||||
const dataSet = image.data;
|
||||
const study = this._getAndCacheStudy(StudyInstanceUID);
|
||||
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
|
||||
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
||||
return parsingUtils.floatArray(dataSet, 'x00181065');
|
||||
Object.assign(instance, naturalizedDataset);
|
||||
|
||||
if (options.server) {
|
||||
await this._checkBulkDataAndInlineBinaries(instance, options.server);
|
||||
}
|
||||
|
||||
return image.instance.frameTimeVector;
|
||||
return instance;
|
||||
}
|
||||
|
||||
getFrameTime(image) {
|
||||
const dataSet = image.data;
|
||||
addImageIdToUIDs(imageId, uids) {
|
||||
// This method is a fallback for when you don't have WADO-URI or WADO-RS.
|
||||
// You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here.
|
||||
// An example would be dicom hosted at some random site.
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
// Frame Increment Pointer points to Frame Time (0018,1063) field or is not defined (for addtional flexibility).
|
||||
// Yet another value is possible for this field (5200,9230 for Multi-frame Functional Groups)
|
||||
// but that case is currently not supported.
|
||||
return dataSet.floatString('x00181063', -1);
|
||||
this.imageIdToUIDs.set(imageId, uids);
|
||||
}
|
||||
|
||||
_getAndCacheStudy(StudyInstanceUID) {
|
||||
const studies = this.studies;
|
||||
|
||||
let study = studies.get(StudyInstanceUID);
|
||||
|
||||
if (!study) {
|
||||
study = { series: new Map() };
|
||||
studies.set(StudyInstanceUID, study);
|
||||
}
|
||||
|
||||
return image.instance.frameTime;
|
||||
return study;
|
||||
}
|
||||
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
|
||||
let series = study.series.get(SeriesInstanceUID);
|
||||
|
||||
if (!series) {
|
||||
series = { instances: new Map() };
|
||||
study.series.set(SeriesInstanceUID, series);
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the related metadata for missing fields given a specified image
|
||||
*
|
||||
* @param image
|
||||
*/
|
||||
updateMetadata(image) {
|
||||
const imageMetadata = this.metadataLookup.get(image.imageId);
|
||||
if (!imageMetadata) {
|
||||
_getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
||||
let instance = series.instances.get(SOPInstanceUID);
|
||||
|
||||
if (!instance) {
|
||||
instance = {};
|
||||
series.instances.set(SOPInstanceUID, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async _checkBulkDataAndInlineBinaries(instance, server) {
|
||||
await fetchOverlayData(instance, server);
|
||||
|
||||
if (instance.PhotometricInterpretation === 'PALETTE COLOR') {
|
||||
await fetchPaletteColorLookupTableData(instance, server);
|
||||
}
|
||||
}
|
||||
|
||||
_getInstance(imageId) {
|
||||
const uids = this._getUIDsFromImageID(imageId);
|
||||
|
||||
if (!uids) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageMetadata.patient.age =
|
||||
imageMetadata.patient.age ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00101010');
|
||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = uids;
|
||||
|
||||
imageMetadata.instance.rows = imageMetadata.instance.rows || image.rows;
|
||||
imageMetadata.instance.columns =
|
||||
imageMetadata.instance.columns || image.columns;
|
||||
|
||||
imageMetadata.instance.sopClassUid =
|
||||
imageMetadata.instance.sopClassUid ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00080016');
|
||||
imageMetadata.instance.sopInstanceUid =
|
||||
imageMetadata.instance.sopInstanceUid ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00080018');
|
||||
|
||||
imageMetadata.instance.pixelSpacing =
|
||||
imageMetadata.instance.pixelSpacing ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00280030');
|
||||
imageMetadata.instance.frameOfReferenceUID =
|
||||
imageMetadata.instance.frameOfReferenceUID ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200052');
|
||||
imageMetadata.instance.imageOrientationPatient =
|
||||
imageMetadata.instance.imageOrientationPatient ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200037');
|
||||
imageMetadata.instance.imagePositionPatient =
|
||||
imageMetadata.instance.imagePositionPatient ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200032');
|
||||
|
||||
imageMetadata.instance.sliceThickness =
|
||||
imageMetadata.instance.sliceThickness ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00180050');
|
||||
imageMetadata.instance.sliceLocation =
|
||||
imageMetadata.instance.sliceLocation ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00201041');
|
||||
imageMetadata.instance.tablePosition =
|
||||
imageMetadata.instance.tablePosition ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00189327');
|
||||
imageMetadata.instance.spacingBetweenSlices =
|
||||
imageMetadata.instance.spacingBetweenSlices ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00180088');
|
||||
|
||||
imageMetadata.instance.lossyImageCompression =
|
||||
imageMetadata.instance.lossyImageCompression ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00282110');
|
||||
imageMetadata.instance.lossyImageCompressionRatio =
|
||||
imageMetadata.instance.lossyImageCompressionRatio ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00282112');
|
||||
|
||||
imageMetadata.instance.frameIncrementPointer =
|
||||
imageMetadata.instance.frameIncrementPointer ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00280009');
|
||||
imageMetadata.instance.frameTime =
|
||||
imageMetadata.instance.frameTime ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00181063');
|
||||
imageMetadata.instance.frameTimeVector =
|
||||
imageMetadata.instance.frameTimeVector ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00181065');
|
||||
|
||||
if (
|
||||
(image.data || image.instance) &&
|
||||
!imageMetadata.instance.multiframeMetadata
|
||||
) {
|
||||
imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(
|
||||
image
|
||||
);
|
||||
}
|
||||
|
||||
imageMetadata.imagePlane =
|
||||
imageMetadata.imagePlane || this.getImagePlane(imageMetadata.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and returns the imagePlane given the metadata instance
|
||||
*
|
||||
* @param metadataInstance The metadata instance (InstanceMetadata class) containing information to construct imagePlane
|
||||
* @returns imagePlane The constructed imagePlane to be used in viewer easily
|
||||
*/
|
||||
getImagePlane(instance) {
|
||||
if (
|
||||
!instance.rows ||
|
||||
!instance.columns ||
|
||||
!instance.pixelSpacing ||
|
||||
!instance.frameOfReferenceUID ||
|
||||
!instance.imageOrientationPatient ||
|
||||
!instance.imagePositionPatient
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageOrientation = instance.imageOrientationPatient.split('\\');
|
||||
const imagePosition = instance.imagePositionPatient.split('\\');
|
||||
|
||||
let columnPixelSpacing = 1.0;
|
||||
let rowPixelSpacing = 1.0;
|
||||
if (instance.pixelSpacing) {
|
||||
const split = instance.pixelSpacing.split('\\');
|
||||
rowPixelSpacing = parseFloat(split[0]);
|
||||
columnPixelSpacing = parseFloat(split[1]);
|
||||
}
|
||||
|
||||
return {
|
||||
frameOfReferenceUID: instance.frameOfReferenceUID,
|
||||
rows: instance.rows,
|
||||
columns: instance.columns,
|
||||
rowCosines: [
|
||||
parseFloat(imageOrientation[0]),
|
||||
parseFloat(imageOrientation[1]),
|
||||
parseFloat(imageOrientation[2]),
|
||||
],
|
||||
columnCosines: [
|
||||
parseFloat(imageOrientation[3]),
|
||||
parseFloat(imageOrientation[4]),
|
||||
parseFloat(imageOrientation[5]),
|
||||
],
|
||||
imagePositionPatient: [
|
||||
parseFloat(imagePosition[0]),
|
||||
parseFloat(imagePosition[1]),
|
||||
parseFloat(imagePosition[2]),
|
||||
],
|
||||
rowPixelSpacing,
|
||||
columnPixelSpacing,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function extracts miltiframe information from a dicomParser.DataSet object.
|
||||
*
|
||||
* @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found.
|
||||
* @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc).
|
||||
*/
|
||||
getMultiframeModuleMetadata(image) {
|
||||
const imageInfo = {
|
||||
isMultiframeImage: false,
|
||||
frameIncrementPointer: null,
|
||||
numberOfFrames: 0,
|
||||
frameTime: 0,
|
||||
frameTimeVector: null,
|
||||
averageFrameRate: 0, // backwards compatibility only... it might be useless in the future
|
||||
};
|
||||
|
||||
let frameTime;
|
||||
|
||||
const numberOfFrames = this.getFromImage(
|
||||
image,
|
||||
'intString',
|
||||
'x00280008',
|
||||
'numberOfFrames',
|
||||
-1
|
||||
return this._getInstanceData(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
if (numberOfFrames > 0) {
|
||||
// set multi-frame image indicator
|
||||
imageInfo.isMultiframeImage = true;
|
||||
imageInfo.numberOfFrames = numberOfFrames;
|
||||
get(query, imageId, options = { fallback: false }) {
|
||||
const instance = this._getInstance(imageId);
|
||||
|
||||
// (0028,0009) = Frame Increment Pointer
|
||||
const frameIncrementPointer = this.getFrameIncrementPointer(image);
|
||||
if (query === INSTANCE) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (frameIncrementPointer === 'frameTimeVector') {
|
||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
||||
const frameTimeVector = this.getFrameTimeVector(image);
|
||||
return this.getTagFromInstance(query, instance, options);
|
||||
}
|
||||
|
||||
if (frameTimeVector instanceof Array && frameTimeVector.length > 0) {
|
||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
||||
imageInfo.frameTimeVector = frameTimeVector;
|
||||
frameTime =
|
||||
frameTimeVector.reduce((a, b) => a + b) / frameTimeVector.length;
|
||||
imageInfo.averageFrameRate = 1000 / frameTime;
|
||||
}
|
||||
} else if (
|
||||
frameIncrementPointer === 'frameTime' ||
|
||||
frameIncrementPointer === ''
|
||||
) {
|
||||
frameTime = this.getFrameTime(image);
|
||||
getTag(query, imageId, options) {
|
||||
return this.get(query, imageId, options);
|
||||
}
|
||||
|
||||
if (frameTime > 0) {
|
||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
||||
imageInfo.frameTime = frameTime;
|
||||
imageInfo.averageFrameRate = 1000 / frameTime;
|
||||
}
|
||||
getInstance(imageId) {
|
||||
return this.get(INSTANCE, imageId);
|
||||
}
|
||||
|
||||
getTagFromInstance(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance,
|
||||
options = { fallback: false }
|
||||
) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If its a naturalized dcmjs tag present on the instance, return.
|
||||
if (instance[naturalizedTagOrWADOImageLoaderTag]) {
|
||||
return instance[naturalizedTagOrWADOImageLoaderTag];
|
||||
}
|
||||
|
||||
if (options.fallback) {
|
||||
// Perhaps the tag has fallbacks?
|
||||
const fallbackTag = getFallbackTagFromInstance(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance
|
||||
);
|
||||
|
||||
if (fallbackTag) {
|
||||
return fallbackTag;
|
||||
}
|
||||
}
|
||||
|
||||
return imageInfo;
|
||||
// Maybe its a legacy CornerstoneWADOImageLoader tag then:
|
||||
return this._getCornerstoneWADOImageLoaderTag(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bound reference to the provider function.
|
||||
*/
|
||||
getProvider() {
|
||||
let provider = this._provider;
|
||||
if (typeof this._provider !== FUNCTION) {
|
||||
provider = this.provider.bind(this);
|
||||
this._provider = provider;
|
||||
_getCornerstoneWADOImageLoaderTag(wadoImageLoaderTag, instance) {
|
||||
let metadata;
|
||||
|
||||
switch (wadoImageLoaderTag) {
|
||||
case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE:
|
||||
const { SeriesDate, SeriesTime } = instance;
|
||||
|
||||
let seriesDate;
|
||||
let seriesTime;
|
||||
|
||||
if (SeriesDate) {
|
||||
seriesDate = dicomParser.parseDA(SeriesDate);
|
||||
}
|
||||
|
||||
if (SeriesTime) {
|
||||
seriesTime = dicomParser.parseTM(SeriesTime);
|
||||
}
|
||||
|
||||
metadata = {
|
||||
modality: instance.Modality,
|
||||
seriesInstanceUID: instance.SeriesInstanceUID,
|
||||
seriesNumber: instance.SeriesNumber,
|
||||
studyInstanceUID: instance.StudyInstanceUID,
|
||||
seriesDate,
|
||||
seriesTime,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.PATIENT_STUDY_MODULE:
|
||||
metadata = {
|
||||
patientAge: instance.PatientAge,
|
||||
patientSize: instance.PatientSize,
|
||||
patientWeight: instance.PatientWeight,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE:
|
||||
const { ImageOrientationPatient } = instance;
|
||||
|
||||
// Fallback for DX images.
|
||||
const PixelSpacing = getFallbackTagFromInstance(
|
||||
'PixelSpacing',
|
||||
instance
|
||||
);
|
||||
|
||||
let rowPixelSpacing;
|
||||
let columnPixelSpacing;
|
||||
|
||||
let rowCosines;
|
||||
let columnCosines;
|
||||
|
||||
if (PixelSpacing) {
|
||||
rowPixelSpacing = PixelSpacing[0];
|
||||
columnPixelSpacing = PixelSpacing[1];
|
||||
}
|
||||
|
||||
if (ImageOrientationPatient) {
|
||||
rowCosines = ImageOrientationPatient.slice(0, 3);
|
||||
columnCosines = ImageOrientationPatient.slice(3, 6);
|
||||
}
|
||||
|
||||
metadata = {
|
||||
frameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
rows: instance.Rows,
|
||||
columns: instance.Columns,
|
||||
imageOrientationPatient: ImageOrientationPatient,
|
||||
rowCosines,
|
||||
columnCosines,
|
||||
imagePositionPatient: instance.ImagePositionPatient,
|
||||
sliceThickness: instance.SliceThickness,
|
||||
sliceLocation: instance.SliceLocation,
|
||||
pixelSpacing: PixelSpacing,
|
||||
rowPixelSpacing,
|
||||
columnPixelSpacing,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE:
|
||||
metadata = {
|
||||
samplesPerPixel: instance.SamplesPerPixel,
|
||||
photometricInterpretation: instance.PhotometricInterpretation,
|
||||
rows: instance.Rows,
|
||||
columns: instance.Columns,
|
||||
bitsAllocated: instance.BitsAllocated,
|
||||
bitsStored: instance.BitsStored,
|
||||
highBit: instance.HighBit,
|
||||
pixelRepresentation: instance.PixelRepresentation,
|
||||
planarConfiguration: instance.PlanarConfiguration,
|
||||
pixelAspectRatio: instance.PixelAspectRatio,
|
||||
smallestPixelValue: instance.SmallestPixelValue,
|
||||
largestPixelValue: instance.LargestPixelValue,
|
||||
redPaletteColorLookupTableDescriptor:
|
||||
instance.RedPaletteColorLookupTableDescriptor,
|
||||
greenPaletteColorLookupTableDescriptor:
|
||||
instance.GreenPaletteColorLookupTableDescriptor,
|
||||
bluePaletteColorLookupTableDescriptor:
|
||||
instance.BluePaletteColorLookupTableDescriptor,
|
||||
redPaletteColorLookupTableData:
|
||||
instance.RedPaletteColorLookupTableData,
|
||||
greenPaletteColorLookupTableData:
|
||||
instance.GreenPaletteColorLookupTableData,
|
||||
bluePaletteColorLookupTableData:
|
||||
instance.BluePaletteColorLookupTableData,
|
||||
};
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE:
|
||||
const { WindowCenter, WindowWidth } = instance;
|
||||
|
||||
const windowCenter = Array.isArray(WindowCenter)
|
||||
? WindowCenter
|
||||
: [WindowCenter];
|
||||
const windowWidth = Array.isArray(WindowWidth)
|
||||
? WindowWidth
|
||||
: [WindowWidth];
|
||||
|
||||
metadata = {
|
||||
windowCenter,
|
||||
windowWidth,
|
||||
};
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE:
|
||||
metadata = {
|
||||
rescaleIntercept: instance.RescaleIntercept,
|
||||
rescaleSlope: instance.RescaleSlope,
|
||||
rescaleType: instance.RescaleType,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.SOP_COMMON_MODULE:
|
||||
metadata = {
|
||||
sopClassUID: instance.SOPClassUID,
|
||||
sopInstanceUID: instance.SOPInstanceUID,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.PET_ISOTOPE_MODULE:
|
||||
const { RadiopharmaceuticalInformationSequence } = instance;
|
||||
|
||||
if (RadiopharmaceuticalInformationSequence) {
|
||||
const RadiopharmaceuticalInformation = Array.isArray(
|
||||
RadiopharmaceuticalInformationSequence
|
||||
)
|
||||
? RadiopharmaceuticalInformationSequence[0]
|
||||
: RadiopharmaceuticalInformationSequence;
|
||||
|
||||
const {
|
||||
RadiopharmaceuticalStartTime,
|
||||
RadionuclideTotalDose,
|
||||
RadionuclideHalfLife,
|
||||
} = RadiopharmaceuticalInformation;
|
||||
|
||||
const radiopharmaceuticalInfo = {
|
||||
radiopharmaceuticalStartTime: dicomParser.parseTM(
|
||||
RadiopharmaceuticalStartTime
|
||||
),
|
||||
radionuclideTotalDose: RadionuclideTotalDose,
|
||||
radionuclideHalfLife: RadionuclideHalfLife,
|
||||
};
|
||||
metadata = {
|
||||
radiopharmaceuticalInfo,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE:
|
||||
metadata = [];
|
||||
|
||||
for (
|
||||
let overlayGroup = 0x00;
|
||||
overlayGroup <= 0x1e;
|
||||
overlayGroup += 0x02
|
||||
) {
|
||||
let groupStr = `60${overlayGroup.toString(16)}`;
|
||||
|
||||
if (groupStr.length === 3) {
|
||||
groupStr = `600${overlayGroup.toString(16)}`;
|
||||
}
|
||||
|
||||
const OverlayDataTag = `${groupStr}3000`;
|
||||
const OverlayData = instance[OverlayDataTag];
|
||||
|
||||
if (!OverlayData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const OverlayRowsTag = `${groupStr}0010`;
|
||||
const OverlayColumnsTag = `${groupStr}0011`;
|
||||
const OverlayType = `${groupStr}0040`;
|
||||
const OverlayOriginTag = `${groupStr}0050`;
|
||||
const OverlayDescriptionTag = `${groupStr}0022`;
|
||||
const OverlayLabelTag = `${groupStr}1500`;
|
||||
const ROIAreaTag = `${groupStr}1301`;
|
||||
const ROIMeanTag = `${groupStr}1302`;
|
||||
const ROIStandardDeviationTag = `${groupStr}1303`;
|
||||
const OverlayOrigin = instance[OverlayOriginTag];
|
||||
|
||||
const overlay = {
|
||||
rows: instance[OverlayRowsTag],
|
||||
columns: instance[OverlayColumnsTag],
|
||||
type: instance[OverlayType],
|
||||
x: OverlayOrigin[0],
|
||||
y: OverlayOrigin[1],
|
||||
pixelData: OverlayData,
|
||||
description: instance[OverlayDescriptionTag],
|
||||
label: instance[OverlayLabelTag],
|
||||
roiArea: instance[ROIAreaTag],
|
||||
roiMean: instance[ROIMeanTag],
|
||||
roiStandardDeviation: instance[ROIStandardDeviationTag],
|
||||
};
|
||||
|
||||
metadata.push(overlay);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WADO_IMAGE_LOADER_TAGS.PATIENT_MODULE:
|
||||
const { PatientName } = instance;
|
||||
|
||||
let patientName;
|
||||
if (PatientName) {
|
||||
patientName = PatientName.Alphabetic;
|
||||
}
|
||||
|
||||
metadata = {
|
||||
patientName,
|
||||
patientId: instance.PatientID,
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case WADO_IMAGE_LOADER_TAGS.GENERAL_IMAGE_MODULE:
|
||||
metadata = {
|
||||
instanceNumber: instance.InstanceNumber,
|
||||
lossyImageCompression: instance.LossyImageCompression,
|
||||
lossyImageCompressionRatio: instance.LossyImageCompressionRatio,
|
||||
lossyImageCompressionMethod: instance.LossyImageCompressionMethod,
|
||||
};
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.GENERAL_STUDY_MODULE:
|
||||
metadata = {
|
||||
studyDescription: instance.StudyDescription,
|
||||
studyDate: instance.StudyDate,
|
||||
studyTime: instance.StudyTime,
|
||||
accessionNumber: instance.AccessionNumber,
|
||||
};
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.CINE_MODULE:
|
||||
metadata = {
|
||||
frameTime: instance.FrameTime,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return provider;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up metadata for Cornerstone Tools given a specified type and imageId
|
||||
* A type may be, e.g. 'study', or 'patient', or 'imagePlaneModule'. These types
|
||||
* are keys in the stored metadata objects.
|
||||
*
|
||||
* @param type
|
||||
* @param imageId
|
||||
* @returns {Object} Relevant metadata of the specified type
|
||||
*/
|
||||
provider(type, imageId) {
|
||||
const imageMetadata = this.metadataLookup.get(imageId);
|
||||
if (!imageMetadata) {
|
||||
_getInstanceData(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) {
|
||||
const study = this.studies.get(StudyInstanceUID);
|
||||
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageMetadata.hasOwnProperty(type)) {
|
||||
return imageMetadata[type];
|
||||
const series = study.series.get(SeriesInstanceUID);
|
||||
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = series.instances.get(SOPInstanceUID);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
_getUIDsFromImageID(imageId) {
|
||||
if (imageId.includes('wadors:')) {
|
||||
const strippedImageId = imageId.split('studies/')[1];
|
||||
const splitImageId = strippedImageId.split('/');
|
||||
|
||||
return {
|
||||
StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series'
|
||||
SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'
|
||||
SOPInstanceUID: splitImageId[4],
|
||||
};
|
||||
}
|
||||
if (imageId.includes('wado?requestType=WADO')) {
|
||||
const qs = queryString.parse(imageId);
|
||||
|
||||
return {
|
||||
StudyInstanceUID: qs.studyUID,
|
||||
SeriesInstanceUID: qs.seriesUID,
|
||||
SOPInstanceUID: qs.objectUID,
|
||||
};
|
||||
} else {
|
||||
// Maybe its a non-standard imageId
|
||||
return this.imageIdToUIDs.get(imageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MetadataProvider;
|
||||
const metadataProvider = new MetadataProvider();
|
||||
|
||||
export default metadataProvider;
|
||||
|
||||
const WADO_IMAGE_LOADER_TAGS = {
|
||||
// CornerstoneWADOImageLoader specific
|
||||
GENERAL_SERIES_MODULE: 'generalSeriesModule',
|
||||
PATIENT_STUDY_MODULE: 'patientStudyModule',
|
||||
IMAGE_PLANE_MODULE: 'imagePlaneModule',
|
||||
IMAGE_PIXEL_MODULE: 'imagePixelModule',
|
||||
VOI_LUT_MODULE: 'voiLutModule',
|
||||
MODALITY_LUT_MODULE: 'modalityLutModule',
|
||||
SOP_COMMON_MODULE: 'sopCommonModule',
|
||||
PET_ISOTOPE_MODULE: 'petIsotopeModule',
|
||||
OVERLAY_PLANE_MODULE: 'overlayPlaneModule',
|
||||
|
||||
// react-cornerstone-viewport specifc
|
||||
PATIENT_MODULE: 'patientModule',
|
||||
GENERAL_IMAGE_MODULE: 'generalImageModule',
|
||||
GENERAL_STUDY_MODULE: 'generalStudyModule',
|
||||
CINE_MODULE: 'cineModule',
|
||||
};
|
||||
|
||||
const INSTANCE = 'instance';
|
||||
@@ -1,9 +1,8 @@
|
||||
import { studyMetadataManager, updateMetaDataManager } from '../utils';
|
||||
import { studyMetadataManager } from '../utils';
|
||||
|
||||
import OHIFError from './OHIFError';
|
||||
import { StudyMetadata } from './metadata/StudyMetadata';
|
||||
import { StudyMetadataSource } from './StudyMetadataSource.js';
|
||||
import { StudySummary } from './metadata/StudySummary';
|
||||
import { retrieveStudyMetadata } from '../studies/retrieveStudyMetadata.js';
|
||||
|
||||
export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
@@ -19,12 +18,12 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
|
||||
/**
|
||||
* Load study info (OHIF.viewer.Studies) and study metadata (OHIF.viewer.StudyMetadataList) for a given study.
|
||||
* @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object.
|
||||
* @param {StudyMetadata} study StudyMetadata object.
|
||||
*/
|
||||
loadStudy(study) {
|
||||
if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) {
|
||||
if (!(study instanceof StudyMetadata)) {
|
||||
throw new OHIFError(
|
||||
'OHIFStudyMetadataSource::loadStudy study is not an instance of StudySummary or StudyMetadata'
|
||||
'OHIFStudyMetadataSource::loadStudy study is not an instance of StudyMetadata'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
|
||||
if (study instanceof StudyMetadata) {
|
||||
const alreadyLoaded = OHIF.viewer.Studies.findBy({
|
||||
studyInstanceUid: studyInstanceUID,
|
||||
StudyInstanceUID: studyInstanceUID,
|
||||
});
|
||||
|
||||
if (!alreadyLoaded) {
|
||||
@@ -49,7 +48,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
// Create study metadata object
|
||||
const studyMetadata = new StudyMetadata(
|
||||
studyInfo,
|
||||
studyInfo.studyInstanceUid
|
||||
studyInfo.StudyInstanceUID
|
||||
);
|
||||
|
||||
// Get Study display sets
|
||||
@@ -72,10 +71,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
// Set some studyInfo properties
|
||||
studyInfo.selected = true;
|
||||
studyInfo.displaySets = studyMetadata.getDisplaySets();
|
||||
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager(studyInfo);
|
||||
|
||||
studyMetadataManager.add(studyMetadata);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import StudySummary from './StudySummary';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const STRING = 'string';
|
||||
const propertyReplacementMap = {
|
||||
modalities: 'ModalitiesInStudy',
|
||||
patientBirthdate: 'PatientBirthDate',
|
||||
};
|
||||
|
||||
/**
|
||||
* OHIF Viewers specialized version of StudySummary class
|
||||
*/
|
||||
export class OHIFStudySummary extends StudySummary {
|
||||
// @Override
|
||||
addTags(tagMap) {
|
||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||
const _tagMap = Object.create(null);
|
||||
for (let property in tagMap) {
|
||||
if (_hasOwn.call(tagMap, property)) {
|
||||
let standardProperty = OHIFStudySummary.getStandardPropertyName(
|
||||
property
|
||||
);
|
||||
if (standardProperty) {
|
||||
_tagMap[standardProperty] = tagMap[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
super.addTags(_tagMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a non-standard, OHIF specific, DICOM property name into a standard one.
|
||||
* @param {string} property A string representing a non-conforming keyword.
|
||||
* @returns {string|undefined} Returns a standard-conforming property name.
|
||||
*/
|
||||
static getStandardPropertyName(property) {
|
||||
let standardProperty;
|
||||
if (typeof property === STRING && property.charAt(0) !== '_') {
|
||||
if (property in propertyReplacementMap) {
|
||||
standardProperty = propertyReplacementMap[propertyReplacementMap];
|
||||
} else {
|
||||
standardProperty = property
|
||||
.replace(/^sop/, 'SOP')
|
||||
.replace(/Uid$/, 'UID')
|
||||
.replace(/Id$/, 'ID');
|
||||
standardProperty =
|
||||
standardProperty.charAt(0).toUpperCase() + standardProperty.substr(1);
|
||||
}
|
||||
}
|
||||
return standardProperty;
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,8 @@ class BaseLoadingListener {
|
||||
}
|
||||
|
||||
_getProgressId() {
|
||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||
return 'StackProgress:' + displaySetInstanceUid;
|
||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||
return 'StackProgress:' + displaySetInstanceUID;
|
||||
}
|
||||
|
||||
_clearProgress() {
|
||||
@@ -362,7 +362,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
|
||||
_logProgress() {
|
||||
const totalFramesCount = this.stack.imageIds.length;
|
||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||
let progressBar = '[';
|
||||
|
||||
for (let i = 0; i < totalFramesCount; i++) {
|
||||
@@ -371,7 +371,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
}
|
||||
|
||||
progressBar += ']';
|
||||
log.info(`${displaySetInstanceUid}: ${progressBar}`);
|
||||
log.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,12 +388,12 @@ class StudyLoadingListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
||||
const displaySetInstanceUID = stack.displaySetInstanceUID;
|
||||
|
||||
if (!this.listeners[displaySetInstanceUid]) {
|
||||
if (!this.listeners[displaySetInstanceUID]) {
|
||||
const listener = this._createListener(stack, stackMetaData);
|
||||
if (listener) {
|
||||
this.listeners[displaySetInstanceUid] = listener;
|
||||
this.listeners[displaySetInstanceUID] = listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,12 +424,12 @@ class StudyLoadingListener {
|
||||
}
|
||||
|
||||
clear() {
|
||||
const displaySetInstanceUids = Object.keys(this.listeners);
|
||||
const length = displaySetInstanceUids.length;
|
||||
const displaySetInstanceUIDs = Object.keys(this.listeners);
|
||||
const length = displaySetInstanceUIDs.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const displaySetInstanceUid = displaySetInstanceUids[i];
|
||||
const displaySet = this.listeners[displaySetInstanceUid];
|
||||
const displaySetInstanceUID = displaySetInstanceUIDs[i];
|
||||
const displaySet = this.listeners[displaySetInstanceUID];
|
||||
|
||||
displaySet.destroy();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export class StudyMetadataSource {
|
||||
|
||||
/**
|
||||
* Load study info and study metadata for a given study into the viewer.
|
||||
* @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object.
|
||||
* @param {StudyMetadata} study StudyMetadata object.
|
||||
*/
|
||||
loadStudy(study) {
|
||||
/**
|
||||
|
||||
@@ -94,17 +94,23 @@ export class StudyPrefetcher {
|
||||
}
|
||||
|
||||
getStudy(image) {
|
||||
const studyMetadata = cornerstone.metaData.get('study', image.imageId);
|
||||
const StudyInstanceUID = cornerstone.metaData.get(
|
||||
'StudyInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
return OHIF.viewer.Studies.find(
|
||||
study => study.studyInstanceUid === studyMetadata.studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
getSeries(study, image) {
|
||||
const seriesMetadata = cornerstone.metaData.get('series', image.imageId);
|
||||
const SeriesInstanceUID = cornerstone.metaData.get(
|
||||
'SeriesInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
|
||||
|
||||
return studyMetadata.getSeriesByUID(seriesMetadata.seriesInstanceUid);
|
||||
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
getInstance(series, image) {
|
||||
@@ -112,13 +118,13 @@ export class StudyPrefetcher {
|
||||
'instance',
|
||||
image.imageId
|
||||
);
|
||||
return series.getInstanceByUID(instanceMetadata.sopInstanceUid);
|
||||
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
|
||||
}
|
||||
|
||||
getActiveDisplaySet(displaySets, instance) {
|
||||
return displaySets.find(displaySet => {
|
||||
return displaySet.images.some(displaySetImage => {
|
||||
return displaySetImage.sopInstanceUid === instance.sopInstanceUid;
|
||||
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Only imported in environment w/ `window`
|
||||
// So we need to mock these for tests
|
||||
import Mousetrap from 'mousetrap';
|
||||
import pausePlugin from 'mousetrap/plugins/pause/mousetrap-pause.js';
|
||||
import recordPlugin from 'mousetrap/plugins/record/mousetrap-record.js';
|
||||
|
||||
// import pausePlugin from './pausePlugin.js';
|
||||
// import recordPlugin from './recordPlugin.js';
|
||||
|
||||
// // // TODO: May need to bind these so Mousetrap = this in plugins;
|
||||
// pausePlugin(Mousetrap);
|
||||
// recordPlugin(Mousetrap);
|
||||
|
||||
// console.log(Mousetrap);
|
||||
// console.log(Object.keys(Mousetrap));
|
||||
|
||||
export default Mousetrap;
|
||||
@@ -1,15 +1,10 @@
|
||||
import {
|
||||
InstanceMetadata,
|
||||
SeriesMetadata,
|
||||
StudyMetadata,
|
||||
StudySummary,
|
||||
} from './metadata';
|
||||
import { InstanceMetadata, SeriesMetadata, StudyMetadata } from './metadata';
|
||||
|
||||
import CommandsManager from './CommandsManager.js';
|
||||
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
||||
import HotkeysManager from './HotkeysManager.js';
|
||||
import ImageSet from './ImageSet';
|
||||
import MetadataProvider from './MetadataProvider.js';
|
||||
import MetadataProvider from './MetadataProvider';
|
||||
import OHIFError from './OHIFError.js';
|
||||
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
||||
import { StackLoadingListener } from './StudyLoadingListener';
|
||||
@@ -31,7 +26,6 @@ export {
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
StudySummary,
|
||||
TypeSafeCollection,
|
||||
OHIFError,
|
||||
StudyMetadataSource,
|
||||
@@ -50,7 +44,6 @@ const classes = {
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
StudySummary,
|
||||
TypeSafeCollection,
|
||||
OHIFError,
|
||||
StudyMetadataSource,
|
||||
|
||||
@@ -8,20 +8,12 @@ import OHIFError from '../OHIFError.js';
|
||||
|
||||
const UNDEFINED = 'undefined';
|
||||
const STRING = 'string';
|
||||
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||
const SERIES_INSTANCE_UID = 'x0020000e';
|
||||
|
||||
export class InstanceMetadata extends Metadata {
|
||||
constructor(data, uid) {
|
||||
super(data, uid);
|
||||
// Initialize Private Properties
|
||||
Object.defineProperties(this, {
|
||||
_sopInstanceUID: {
|
||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null,
|
||||
},
|
||||
_imageId: {
|
||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
||||
enumerable: false,
|
||||
@@ -43,14 +35,14 @@ export class InstanceMetadata extends Metadata {
|
||||
*/
|
||||
_definePublicProperties() {
|
||||
/**
|
||||
* Property: this.sopInstanceUID
|
||||
* Property: this.SOPInstanceUID
|
||||
* Same as this.getSOPInstanceUID()
|
||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||
* sopInstanceCollection.findBy({
|
||||
* sopInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* SOPInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* });
|
||||
*/
|
||||
Object.defineProperty(this, 'sopInstanceUID', {
|
||||
Object.defineProperty(this, 'SOPInstanceUID', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
@@ -67,21 +59,21 @@ export class InstanceMetadata extends Metadata {
|
||||
* Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||
*/
|
||||
getStudyInstanceUID() {
|
||||
return this.getTagValue(STUDY_INSTANCE_UID, null);
|
||||
return this.getTagValue('StudyInstanceUID', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||
*/
|
||||
getSeriesInstanceUID() {
|
||||
return this.getTagValue(SERIES_INSTANCE_UID, null);
|
||||
return this.getTagValue('SeriesInstanceUID', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SOPInstanceUID of the current instance.
|
||||
*/
|
||||
getSOPInstanceUID() {
|
||||
return this._sopInstanceUID;
|
||||
return this.getTagValue('SOPInstanceUID', null);
|
||||
}
|
||||
|
||||
// @TODO: Improve this... (E.g.: blob data)
|
||||
@@ -127,13 +119,6 @@ export class InstanceMetadata extends Metadata {
|
||||
return typeof value === STRING ? parseInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use getTagValue instead.
|
||||
*/
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
return this.getTagValue(tagOrProperty, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be overriden by specialized classes in order to allow client libraries or viewers to take advantage of the Study Metadata API.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { InstanceMetadata } from './InstanceMetadata';
|
||||
import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions.js';
|
||||
import getImageId from '../../utils/getImageId.js';
|
||||
|
||||
export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
@@ -20,7 +19,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: instance.sopInstanceUid,
|
||||
value: instance.SOPInstanceUID,
|
||||
},
|
||||
_study: {
|
||||
configurable: false,
|
||||
@@ -56,16 +55,16 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
return this._cache[tagOrProperty];
|
||||
}
|
||||
|
||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
||||
const instanceData = this._instance.metadata;
|
||||
|
||||
// Search property value in the whole study metadata chain...
|
||||
let rawValue;
|
||||
if (propertyName in this._instance) {
|
||||
rawValue = this._instance[propertyName];
|
||||
} else if (propertyName in this._series) {
|
||||
rawValue = this._series[propertyName];
|
||||
} else if (propertyName in this._study) {
|
||||
rawValue = this._study[propertyName];
|
||||
if (tagOrProperty in instanceData) {
|
||||
rawValue = instanceData[tagOrProperty];
|
||||
} else if (tagOrProperty in this._series) {
|
||||
rawValue = this._series[tagOrProperty];
|
||||
} else if (tagOrProperty in this._study) {
|
||||
rawValue = this._study[tagOrProperty];
|
||||
}
|
||||
|
||||
if (rawValue !== void 0) {
|
||||
@@ -79,12 +78,10 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
|
||||
// Override
|
||||
tagExists(tagOrProperty) {
|
||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
||||
|
||||
return (
|
||||
propertyName in this._instance ||
|
||||
propertyName in this._series ||
|
||||
propertyName in this._study
|
||||
tagOrProperty in this._instance.metadata ||
|
||||
tagOrProperty in this._series ||
|
||||
tagOrProperty in this._study
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,28 +94,4 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
|
||||
return this._imageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static methods
|
||||
*/
|
||||
|
||||
// @TODO: The current mapping of standard DICOM property names to local property names is not optimal.
|
||||
// The inconsistency in property naming makes this function increasingly complex.
|
||||
// A possible solution to improve this would be adapt retriveMetadata names to use DICOM standard names as in dicomTagDescriptions.js
|
||||
static getPropertyName(tagOrProperty) {
|
||||
let propertyName;
|
||||
const tagInfo = DICOMTagDescriptions.find(tagOrProperty);
|
||||
|
||||
if (tagInfo !== void 0) {
|
||||
// This function tries to translate standard DICOM property names into local naming convention.
|
||||
propertyName = tagInfo.keyword
|
||||
.replace(/^SOP/, 'sop')
|
||||
.replace(/UID$/, 'Uid')
|
||||
.replace(/ID$/, 'Id');
|
||||
propertyName =
|
||||
propertyName.charAt(0).toLowerCase() + propertyName.substr(1);
|
||||
}
|
||||
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export class OHIFSeriesMetadata extends SeriesMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: series.seriesInstanceUid,
|
||||
value: series.SeriesInstanceUID,
|
||||
});
|
||||
|
||||
// populate internal list of instances...
|
||||
|
||||
@@ -18,11 +18,11 @@ export class OHIFStudyMetadata extends StudyMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: study.studyInstanceUid,
|
||||
value: study.StudyInstanceUID,
|
||||
});
|
||||
|
||||
// populate internal list of series
|
||||
study.seriesList.forEach(series => {
|
||||
study.series.forEach(series => {
|
||||
this.addSeries(new OHIFSeriesMetadata(series, study));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,10 +83,10 @@ well (again, flexibility and extensibility are design goals). Most implemented
|
||||
methods rely on the implementation of an unimplemented method. For example, the
|
||||
method `getStringValue` from `InstanceMetadata` class, which has indeed been
|
||||
implemented and is meant to retrieve a metadata value as a string, internally
|
||||
calls the `getRawValue` method which _was NOT implemented_ and is meant to query
|
||||
calls the `getTagValue` method which _was NOT implemented_ and is meant to query
|
||||
the internal data structures for the requested metadata value and return it _as
|
||||
is_. Used in that way, an application would not benefit much from the already
|
||||
implemented methods. On the other hand, by simply overriding the `getRawValue`
|
||||
implemented methods. On the other hand, by simply overriding the `getTagValue`
|
||||
method on a specialized class to deal with the intrinsics of its internal data
|
||||
structures, this very application would now benefit from all already implemented
|
||||
methods.
|
||||
@@ -99,12 +99,12 @@ The following code snippet tries to illustrate the idea:
|
||||
|
||||
class InstanceMetadata {
|
||||
[ ... ]
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
getTagValue(tagOrProperty, defaultValue) {
|
||||
// Please implement this method in a specialized subclass...
|
||||
}
|
||||
[ ... ]
|
||||
getStringValue(tagOrProperty, index, defaultValue) {
|
||||
let rawValue = this.getRawValue(tagOrProperty, '');
|
||||
let rawValue = this.getTagValue(tagOrProperty, '');
|
||||
// parse the returned value into a string...
|
||||
[ ... ]
|
||||
return stringValue;
|
||||
@@ -117,11 +117,11 @@ class InstanceMetadata {
|
||||
class MyFancyAppInstanceMetadata extends InstanceMetadata {
|
||||
// Overriding this method will make all methods implemented in the super class
|
||||
// that rely on it to be immediately available...
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
let rawValue;
|
||||
getTagValue(tagOrProperty, defaultValue) {
|
||||
let tagValue;
|
||||
// retrieve raw value from internal data structures...
|
||||
[ ... ]
|
||||
return rawValue;
|
||||
return tagValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +135,9 @@ if (sopInstaceMetadata instanceof MyFancyAppInstanceMetadata) { // true
|
||||
if (sopInstaceMetadata instanceof InstanceMetadata) { // also true
|
||||
// this code will also be executed...
|
||||
}
|
||||
// The following will also work since the internal "getRawValue" call inside
|
||||
// The following will also work since the internal "getTagValue" call inside
|
||||
// "getStringValue" method will now be satisfied... (thanks to the override)
|
||||
let patientName = sopInstaceMetadata.getStringValue('PatientName', '');
|
||||
let PatientName = sopInstaceMetadata.getStringValue('PatientName', '');
|
||||
[ ... ]
|
||||
|
||||
```
|
||||
|
||||
@@ -110,14 +110,14 @@ export class StudyMetadata extends Metadata {
|
||||
return;
|
||||
}
|
||||
|
||||
const sopClassUids = getSopClassUids(series);
|
||||
const sopClassUIDs = getSopClassUIDs(series);
|
||||
|
||||
if (sopClassHandlerModules && sopClassHandlerModules.length > 0) {
|
||||
const displaySet = _getDisplaySetFromSopClassModule(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
study,
|
||||
sopClassUids
|
||||
sopClassUIDs
|
||||
);
|
||||
if (displaySet) {
|
||||
displaySet.sopClassModule = true;
|
||||
@@ -135,10 +135,10 @@ export class StudyMetadata extends Metadata {
|
||||
// series into another display set.
|
||||
const stackableInstances = [];
|
||||
series.forEachInstance(instance => {
|
||||
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
|
||||
if (
|
||||
!isImage(instance.getRawValue('x00080016')) &&
|
||||
!instance.getRawValue('x00280010')
|
||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||
!instance.getTagValue('Rows')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -149,23 +149,23 @@ export class StudyMetadata extends Metadata {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
isClip: true,
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid for drag/drop purposes
|
||||
numImageFrames: instance.getRawValue('x00280008'), // Override the default value of instances.length
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
||||
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else if (isSingleImageModality(instance.modality)) {
|
||||
} else if (isSingleImageModality(instance.Modality)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
||||
sopClassUIDs,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else {
|
||||
@@ -175,9 +175,9 @@ export class StudyMetadata extends Metadata {
|
||||
|
||||
if (stackableInstances.length) {
|
||||
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
||||
displaySet.setAttribute('StudyInstanceUID', study.getStudyInstanceUID());
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
}
|
||||
@@ -416,7 +416,7 @@ export class StudyMetadata extends Metadata {
|
||||
* needs series sorted by the same criteria used for sorting display sets.
|
||||
*/
|
||||
sortSeriesByDisplaySets() {
|
||||
// Object for mapping display sets' index by seriesInstanceUid
|
||||
// Object for mapping display sets' index by SeriesInstanceUID
|
||||
const displaySetsMapping = {};
|
||||
|
||||
// Loop through each display set to create the mapping
|
||||
@@ -428,8 +428,8 @@ export class StudyMetadata extends Metadata {
|
||||
}
|
||||
|
||||
// In case of multiframe studies, just get the first index occurence
|
||||
if (displaySetsMapping[displaySet.seriesInstanceUid] === void 0) {
|
||||
displaySetsMapping[displaySet.seriesInstanceUid] = index;
|
||||
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
|
||||
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -581,8 +581,7 @@ export class StudyMetadata extends Metadata {
|
||||
const dwc = api.DICOMwebClient;
|
||||
|
||||
const isMultiFrame = instance => {
|
||||
// NumberOfFrames (0028,0008)
|
||||
return instance.getRawValue('x00280008') > 1;
|
||||
return instance.getTagValue('NumberOfFrames') > 1;
|
||||
};
|
||||
|
||||
const makeDisplaySet = (series, instances) => {
|
||||
@@ -592,15 +591,15 @@ const makeDisplaySet = (series, instances) => {
|
||||
|
||||
// set appropriate attributes to image set...
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||
seriesDate: seriesData.seriesDate,
|
||||
seriesTime: seriesData.seriesTime,
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
seriesNumber: instance.getRawValue('x00200011'),
|
||||
seriesDescription: instance.getRawValue('x0008103e'),
|
||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||
SeriesDate: seriesData.SeriesDate,
|
||||
SeriesTime: seriesData.SeriesTime,
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
SeriesNumber: instance.getTagValue('SeriesNumber'),
|
||||
SeriesDescription: instance.getTagValue('SeriesDescription'),
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.getRawValue('x00181063'),
|
||||
modality: instance.getRawValue('x00080060'),
|
||||
frameRate: instance.getTagValue('FrameTime'),
|
||||
Modality: instance.getTagValue('Modality'),
|
||||
isMultiFrame: isMultiFrame(instance),
|
||||
});
|
||||
|
||||
@@ -610,19 +609,19 @@ const makeDisplaySet = (series, instances) => {
|
||||
imageSet.sortBy((a, b) => {
|
||||
// Sort by InstanceNumber (0020,0013)
|
||||
return (
|
||||
(parseInt(a.getRawValue('x00200013', 0)) || 0) -
|
||||
(parseInt(b.getRawValue('x00200013', 0)) || 0)
|
||||
(parseInt(a.getTagValue('InstanceNumber', 0)) || 0) -
|
||||
(parseInt(b.getTagValue('InstanceNumber', 0)) || 0)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Include the first image instance number (after sorted)
|
||||
imageSet.setAttribute(
|
||||
'instanceNumber',
|
||||
imageSet.getImage(0).getRawValue('x00200013')
|
||||
'InstanceNumber',
|
||||
imageSet.getImage(0).getTagValue('InstanceNumber')
|
||||
);
|
||||
|
||||
const isReconstructable = isDisplaySetReconstructable(series, instances);
|
||||
const isReconstructable = isDisplaySetReconstructable(instances);
|
||||
|
||||
imageSet.isReconstructable = isReconstructable.value;
|
||||
|
||||
@@ -635,57 +634,57 @@ const makeDisplaySet = (series, instances) => {
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
const isSingleImageModality = modality => {
|
||||
return modality === 'CR' || modality === 'MG' || modality === 'DX';
|
||||
const isSingleImageModality = Modality => {
|
||||
return Modality === 'CR' || Modality === 'MG' || Modality === 'DX';
|
||||
};
|
||||
|
||||
function getSopClassUids(series) {
|
||||
const uniqueSopClassUidsInSeries = new Set();
|
||||
function getSopClassUIDs(series) {
|
||||
const uniqueSopClassUIDsInSeries = new Set();
|
||||
series.forEachInstance(instance => {
|
||||
const instanceSopClassUid = instance.getRawValue('x00080016');
|
||||
const instanceSopClassUID = instance.getTagValue('SOPClassUID');
|
||||
|
||||
uniqueSopClassUidsInSeries.add(instanceSopClassUid);
|
||||
uniqueSopClassUIDsInSeries.add(instanceSopClassUID);
|
||||
});
|
||||
const sopClassUids = Array.from(uniqueSopClassUidsInSeries);
|
||||
const sopClassUIDs = Array.from(uniqueSopClassUIDsInSeries);
|
||||
|
||||
return sopClassUids;
|
||||
return sopClassUIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {SeriesMetadata} series
|
||||
* @param {StudyMetadata} study
|
||||
* @param {string[]} sopClassUids
|
||||
* @param {string[]} sopClassUIDs
|
||||
*/
|
||||
function _getDisplaySetFromSopClassModule(
|
||||
sopClassHandlerExtensions, // TODO: Update Usage
|
||||
series,
|
||||
study,
|
||||
sopClassUids
|
||||
sopClassUIDs
|
||||
) {
|
||||
// TODO: For now only use the plugins if all instances have the same sopClassUid
|
||||
if (sopClassUids.length !== 1) {
|
||||
// TODO: For now only use the plugins if all instances have the same SOPClassUID
|
||||
if (sopClassUIDs.length !== 1) {
|
||||
console.warn(
|
||||
'getDisplaySetFromSopClassPlugin: More than one SOPClassUid in the same series is not yet supported.'
|
||||
'getDisplaySetFromSopClassPlugin: More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs[0];
|
||||
const sopClassHandlerModules = sopClassHandlerExtensions.map(extension => {
|
||||
return extension.module;
|
||||
});
|
||||
|
||||
const handlersForSopClassUid = sopClassHandlerModules.filter(module => {
|
||||
return module.sopClassUids.includes(sopClassUid);
|
||||
const handlersForSopClassUID = sopClassHandlerModules.filter(module => {
|
||||
return module.sopClassUIDs.includes(SOPClassUID);
|
||||
});
|
||||
|
||||
// TODO: Sort by something, so we can determine which plugin to use
|
||||
if (!handlersForSopClassUid || !handlersForSopClassUid.length) {
|
||||
if (!handlersForSopClassUID || !handlersForSopClassUID.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugin = handlersForSopClassUid[0];
|
||||
const plugin = handlersForSopClassUID[0];
|
||||
const headers = DICOMWeb.getAuthorizationHeader();
|
||||
const dicomWebClient = new dwc({
|
||||
url: study.getData().wadoRoot,
|
||||
@@ -698,15 +697,15 @@ function _getDisplaySetFromSopClassModule(
|
||||
dicomWebClient,
|
||||
headers
|
||||
);
|
||||
if (displaySet && !displaySet.modality) {
|
||||
if (displaySet && !displaySet.Modality) {
|
||||
const instance = series.getFirstInstance();
|
||||
displaySet.modality = instance.getRawValue('x00080060');
|
||||
displaySet.Modality = instance.getTagValue('Modality');
|
||||
}
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort series primarily by modality (i.e., series with references to other
|
||||
* Sort series primarily by Modality (i.e., series with references to other
|
||||
* series like SEG, KO or PR are grouped in the end of the list) and then by
|
||||
* series number:
|
||||
*
|
||||
@@ -724,8 +723,8 @@ function _getDisplaySetFromSopClassModule(
|
||||
*/
|
||||
|
||||
function seriesSortingCriteria(a, b) {
|
||||
const isLowPriorityA = isLowPriorityModality(a.modality);
|
||||
const isLowPriorityB = isLowPriorityModality(b.modality);
|
||||
const isLowPriorityA = isLowPriorityModality(a.Modality);
|
||||
const isLowPriorityB = isLowPriorityModality(b.Modality);
|
||||
if (!isLowPriorityA && isLowPriorityB) {
|
||||
return -1;
|
||||
}
|
||||
@@ -742,7 +741,7 @@ function seriesSortingCriteria(a, b) {
|
||||
*/
|
||||
function sortBySeriesNumber(a, b) {
|
||||
const seriesNumberAIsGreaterOrUndefined =
|
||||
a.seriesNumber > b.seriesNumber || (!a.seriesNumber && b.seriesNumber);
|
||||
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
|
||||
|
||||
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Metadata } from './Metadata';
|
||||
import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||
|
||||
/**
|
||||
* Class Definition
|
||||
*/
|
||||
|
||||
export class StudySummary extends Metadata {
|
||||
constructor(tagMap, attributeMap, uid) {
|
||||
// Call the superclass constructor passing an plain object with no prototype to be used as the main "_data" attribute.
|
||||
const _data = Object.create(null);
|
||||
super(_data, uid);
|
||||
|
||||
// Initialize internal tag map if first argument is given.
|
||||
if (tagMap !== void 0) {
|
||||
this.addTags(tagMap);
|
||||
}
|
||||
|
||||
// Initialize internal property map if second argument is given.
|
||||
if (attributeMap !== void 0) {
|
||||
this.setCustomAttributes(attributeMap);
|
||||
}
|
||||
}
|
||||
|
||||
getStudyInstanceUID() {
|
||||
// This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API
|
||||
return this.getTagValue(STUDY_INSTANCE_UID) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append tags to internal tag map.
|
||||
* @param {Object} tagMap An object whose own properties will be used as tag values and appended to internal tag map.
|
||||
*/
|
||||
addTags(tagMap) {
|
||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||
const _data = this._data;
|
||||
for (let tag in tagMap) {
|
||||
if (_hasOwn.call(tagMap, tag)) {
|
||||
const description = DICOMTagDescriptions.find(tag);
|
||||
// When a description is available, use its tag as internal key...
|
||||
if (description) {
|
||||
_data[description.tag] = tagMap[tag];
|
||||
} else {
|
||||
_data[tag] = tagMap[tag];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagExists(tagName) {
|
||||
const _data = this._data;
|
||||
const description = DICOMTagDescriptions.find(tagName);
|
||||
if (description) {
|
||||
return description.tag in _data;
|
||||
}
|
||||
return tagName in _data;
|
||||
}
|
||||
|
||||
getTagValue(tagName) {
|
||||
const _data = this._data;
|
||||
const description = DICOMTagDescriptions.find(tagName);
|
||||
if (description) {
|
||||
return _data[description.tag];
|
||||
}
|
||||
return _data[tagName];
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
export class WadoRsMetaDataBuilder {
|
||||
constructor() {
|
||||
this.tags = {};
|
||||
}
|
||||
|
||||
addTag(tag, value, multi) {
|
||||
this.tags[tag] = {
|
||||
tag,
|
||||
value,
|
||||
multi,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = {};
|
||||
const keys = Object.keys(this.tags);
|
||||
|
||||
keys.forEach(key => {
|
||||
if (!this.tags.hasOwnProperty(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = this.tags[key];
|
||||
const multi = !!tag.multi;
|
||||
let value = tag.value;
|
||||
|
||||
if (value == null || (value.length === 1 && value[0] == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && multi) {
|
||||
value = value.split('\\');
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
json[key] = {
|
||||
Value: value,
|
||||
};
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,9 @@ import { OHIFSeriesMetadata } from './OHIFSeriesMetadata';
|
||||
import { OHIFStudyMetadata } from './OHIFStudyMetadata';
|
||||
import { SeriesMetadata } from './SeriesMetadata';
|
||||
import { StudyMetadata } from './StudyMetadata';
|
||||
import { StudySummary } from './StudySummary';
|
||||
import { WadoRsMetaDataBuilder } from './WadoRsMetaDataBuilder';
|
||||
|
||||
const metadata = {
|
||||
Metadata,
|
||||
StudySummary,
|
||||
WadoRsMetaDataBuilder,
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
@@ -22,8 +18,6 @@ const metadata = {
|
||||
|
||||
export {
|
||||
Metadata,
|
||||
StudySummary,
|
||||
WadoRsMetaDataBuilder,
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import MetadataProvider from './classes/MetadataProvider';
|
||||
import metadataProvider from './classes/MetadataProvider';
|
||||
import {
|
||||
getBoundingBox,
|
||||
pixelToPage,
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from './lib/cornerstone.js';
|
||||
|
||||
const cornerstone = {
|
||||
MetadataProvider,
|
||||
metadataProvider,
|
||||
getBoundingBox,
|
||||
pixelToPage,
|
||||
repositionTextBox,
|
||||
|
||||
@@ -226,7 +226,7 @@ describe('ExtensionManager.js', () => {
|
||||
return {
|
||||
definitions: {
|
||||
exampleDefinition: {
|
||||
commandFn: () => {},
|
||||
commandFn: () => { },
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
|
||||
@@ -6,24 +6,19 @@ import { CustomAttributeRetrievalCallbacks } from './customAttributes';
|
||||
/**
|
||||
* Import Constants
|
||||
*/
|
||||
const { StudySummary, InstanceMetadata } = metadata;
|
||||
const { InstanceMetadata } = metadata;
|
||||
|
||||
/**
|
||||
* Match a Metadata instance against rules using Validate.js for validation.
|
||||
* @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object
|
||||
* @param {InstanceMetadata} metadataInstance Metadata instance object
|
||||
* @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match
|
||||
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
||||
*/
|
||||
const match = (metadataInstance, rules) => {
|
||||
// Make sure the supplied data is valid.
|
||||
if (
|
||||
!(
|
||||
metadataInstance instanceof StudySummary ||
|
||||
metadataInstance instanceof InstanceMetadata
|
||||
)
|
||||
) {
|
||||
if (!(metadataInstance instanceof InstanceMetadata)) {
|
||||
throw new OHIFError(
|
||||
'HPMatcher::match metadataInstance must be an instance of StudySummary or InstanceMetadata'
|
||||
'HPMatcher::match metadataInstance must be an instance of InstanceMetadata'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ProtocolStore } from './protocolStore/classes';
|
||||
/**
|
||||
* Import Constants
|
||||
*/
|
||||
const { StudyMetadata, InstanceMetadata, StudySummary } = metadata;
|
||||
const { StudyMetadata, InstanceMetadata } = metadata;
|
||||
|
||||
// Useful constants
|
||||
const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue';
|
||||
@@ -298,10 +298,7 @@ export default class ProtocolEngine {
|
||||
}
|
||||
|
||||
// Invalid data
|
||||
if (
|
||||
!(priorStudy instanceof StudyMetadata) &&
|
||||
!(priorStudy instanceof StudySummary)
|
||||
) {
|
||||
if (!priorStudy instanceof StudyMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -385,11 +382,9 @@ export default class ProtocolEngine {
|
||||
// This tests to make sure there is actually image data in this instance
|
||||
// TODO: Change this when we add PDF and MPEG support
|
||||
// See https://ohiforg.atlassian.net/browse/LT-227
|
||||
// sopClassUid = x00080016
|
||||
// rows = x00280010
|
||||
if (
|
||||
!isImage(instance.getTagValue('x00080016')) &&
|
||||
!instance.getTagValue('x00280010')
|
||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||
!instance.getTagValue('Rows')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -436,19 +431,19 @@ export default class ProtocolEngine {
|
||||
const currentSOPInstanceUID = instance.getSOPInstanceUID();
|
||||
|
||||
const imageDetails = {
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
sopInstanceUid: currentSOPInstanceUID,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
SOPInstanceUID: currentSOPInstanceUID,
|
||||
currentImageIdIndex: index,
|
||||
matchingScore: totalMatchScore,
|
||||
matchDetails: matchDetails,
|
||||
sortingInfo: {
|
||||
score: totalMatchScore,
|
||||
study:
|
||||
instance.getTagValue('x00080020') +
|
||||
instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
||||
series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
||||
instance: parseInt(instance.getTagValue('x00200013')), // TODO: change for acquisitionTime InstanceNumber = x00200013
|
||||
instance.getTagValue('StudyDate') +
|
||||
instance.getTagValue('StudyTime'),
|
||||
series: parseInt(instance.getTagValue('SeriesNumber')), // TODO: change for seriesDateTime
|
||||
instance: parseInt(instance.getTagValue('InstanceNumber')), // TODO: change for acquisitionTime
|
||||
},
|
||||
};
|
||||
|
||||
@@ -461,7 +456,7 @@ export default class ProtocolEngine {
|
||||
|
||||
// If the instance was found, set the displaySet ID
|
||||
if (displaySet) {
|
||||
imageDetails.displaySetInstanceUid = displaySet.getUID();
|
||||
imageDetails.displaySetInstanceUID = displaySet.getUID();
|
||||
imageDetails.imageId = instance.getImageId();
|
||||
}
|
||||
|
||||
@@ -654,25 +649,25 @@ export default class ProtocolEngine {
|
||||
}
|
||||
|
||||
if (currentMatch && currentMatch.imageId) {
|
||||
currentViewportData.studyInstanceUid = currentMatch.studyInstanceUid;
|
||||
currentViewportData.seriesInstanceUid = currentMatch.seriesInstanceUid;
|
||||
currentViewportData.sopInstanceUid = currentMatch.sopInstanceUid;
|
||||
currentViewportData.StudyInstanceUID = currentMatch.StudyInstanceUID;
|
||||
currentViewportData.SeriesInstanceUID = currentMatch.SeriesInstanceUID;
|
||||
currentViewportData.SOPInstanceUID = currentMatch.SOPInstanceUID;
|
||||
currentViewportData.currentImageIdIndex =
|
||||
currentMatch.currentImageIdIndex;
|
||||
currentViewportData.displaySetInstanceUid =
|
||||
currentMatch.displaySetInstanceUid;
|
||||
currentViewportData.displaySetInstanceUID =
|
||||
currentMatch.displaySetInstanceUID;
|
||||
currentViewportData.imageId = currentMatch.imageId;
|
||||
}
|
||||
|
||||
// @TODO Why should we throw an exception when a best match is not found? This was aborting the whole process.
|
||||
// if (!currentViewportData.displaySetInstanceUid) {
|
||||
// if (!currentViewportData.displaySetInstanceUID) {
|
||||
// throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?');
|
||||
// }
|
||||
|
||||
viewportData.push(currentViewportData);
|
||||
});
|
||||
|
||||
this.setLayout(layoutProps.rows, layoutProps.columns);
|
||||
this.setLayout(layoutProps.Rows, layoutProps.Columns);
|
||||
|
||||
if (typeof this.options.setViewportSpecificData !== 'function') {
|
||||
log.error(
|
||||
|
||||
@@ -6,7 +6,7 @@ const EQUALS_REGEXP = /^equals$/;
|
||||
/**
|
||||
* This Class represents a Rule to be evaluated given a set of attributes
|
||||
* Rules have:
|
||||
* - An attribute (e.g. 'seriesDescription')
|
||||
* - An attribute (e.g. 'SeriesDescription')
|
||||
* - A constraint Object, in the form required by Validate.js:
|
||||
*
|
||||
* rule.constraint = {
|
||||
|
||||
@@ -45,9 +45,9 @@ export default class ViewportStructure {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
// For the typical grid layout, we only need to multiply rows by columns to
|
||||
// For the typical grid layout, we only need to multiply Rows by Columns to
|
||||
// obtain the number of viewports
|
||||
return this.properties.rows * this.properties.columns;
|
||||
return this.properties.Rows * this.properties.Columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ function getDefaultProtocol() {
|
||||
protocol.locked = true;
|
||||
|
||||
const oneByOne = new ViewportStructure('grid', {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
Rows: 1,
|
||||
Columns: 1,
|
||||
});
|
||||
|
||||
const viewport = new Viewport();
|
||||
|
||||
@@ -14,8 +14,8 @@ function getMRTwoByTwoTest() {
|
||||
proto.locked = true;
|
||||
// Use http://localhost:3000/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78
|
||||
|
||||
var studyInstanceUid = new ProtocolMatchingRule(
|
||||
'studyInstanceUid',
|
||||
var StudyInstanceUID = new ProtocolMatchingRule(
|
||||
'StudyInstanceUID',
|
||||
{
|
||||
equals: {
|
||||
value: '1.2.840.113619.2.5.1762583153.215519.978957063.78',
|
||||
@@ -24,30 +24,30 @@ function getMRTwoByTwoTest() {
|
||||
true
|
||||
);
|
||||
|
||||
proto.addProtocolMatchingRule(studyInstanceUid);
|
||||
proto.addProtocolMatchingRule(StudyInstanceUID);
|
||||
|
||||
var oneByTwo = new ViewportStructure('grid', {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
});
|
||||
|
||||
// Stage 1
|
||||
var left = new Viewport();
|
||||
var right = new Viewport();
|
||||
|
||||
var firstSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var firstSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 1,
|
||||
},
|
||||
});
|
||||
|
||||
var secondSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var secondSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 2,
|
||||
},
|
||||
});
|
||||
|
||||
var thirdImage = new ImageMatchingRule('instanceNumber', {
|
||||
var thirdImage = new ImageMatchingRule('InstanceNumber', {
|
||||
equals: {
|
||||
value: 3,
|
||||
},
|
||||
@@ -67,19 +67,19 @@ function getMRTwoByTwoTest() {
|
||||
|
||||
// Stage 2
|
||||
var twoByOne = new ViewportStructure('grid', {
|
||||
rows: 2,
|
||||
columns: 1,
|
||||
Rows: 2,
|
||||
Columns: 1,
|
||||
});
|
||||
var left2 = new Viewport();
|
||||
var right2 = new Viewport();
|
||||
|
||||
var fourthSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var fourthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 4,
|
||||
},
|
||||
});
|
||||
|
||||
var fifthSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var fifthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 5,
|
||||
},
|
||||
@@ -133,8 +133,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -195,8 +195,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -257,8 +257,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -402,8 +402,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -464,8 +464,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -548,8 +548,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -737,8 +737,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
Rows: 1,
|
||||
Columns: 1,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -770,8 +770,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -931,8 +931,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -993,8 +993,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -1044,8 +1044,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -1127,8 +1127,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -1190,8 +1190,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -1275,8 +1275,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@@ -1386,8 +1386,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
|
||||
@@ -18,12 +18,13 @@ import string from './string.js';
|
||||
import studies from './studies/';
|
||||
import ui from './ui';
|
||||
import user from './user.js';
|
||||
import utils from './utils/';
|
||||
import utils, { hotkeys } from './utils/';
|
||||
|
||||
import {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
} from './services';
|
||||
|
||||
const OHIF = {
|
||||
@@ -35,6 +36,7 @@ const OHIF = {
|
||||
ServicesManager,
|
||||
//
|
||||
utils,
|
||||
hotkeys,
|
||||
studies,
|
||||
redux,
|
||||
classes,
|
||||
@@ -55,6 +57,7 @@ const OHIF = {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -66,6 +69,7 @@ export {
|
||||
ServicesManager,
|
||||
//
|
||||
utils,
|
||||
hotkeys,
|
||||
studies,
|
||||
redux,
|
||||
classes,
|
||||
@@ -85,6 +89,7 @@ export {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -13,8 +13,10 @@ describe('Top level exports', () => {
|
||||
'UINotificationService',
|
||||
'UIModalService',
|
||||
'UIDialogService',
|
||||
'MeasurementService',
|
||||
//
|
||||
'utils',
|
||||
'hotkeys',
|
||||
'studies',
|
||||
'redux',
|
||||
'classes',
|
||||
|
||||
@@ -21,7 +21,7 @@ function getBoundingBox(context, textLines, x, y, options) {
|
||||
// Get the text width in the current font
|
||||
const width = context.measureText(text).width;
|
||||
|
||||
// Find the maximum with for all the text rows;
|
||||
// Find the maximum with for all the text Rows;
|
||||
maxWidth = Math.max(maxWidth, width);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ import guid from '../../utils/guid';
|
||||
import studyMetadataManager from '../../utils/studyMetadataManager';
|
||||
import { measurementApiDefaultConfig } from './../configuration.js';
|
||||
|
||||
|
||||
const configuration = {
|
||||
...measurementApiDefaultConfig
|
||||
...measurementApiDefaultConfig,
|
||||
};
|
||||
|
||||
export default class MeasurementApi {
|
||||
@@ -99,8 +98,8 @@ export default class MeasurementApi {
|
||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
// Stop here if the metadata for the measurement's study is not loaded yet
|
||||
const { studyInstanceUid } = measurement;
|
||||
const metadata = studyMetadataManager.get(studyInstanceUid);
|
||||
const { StudyInstanceUID } = measurement;
|
||||
const metadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
if (!metadata) return;
|
||||
|
||||
// Iterate each child tool if the current tool has children
|
||||
@@ -221,7 +220,7 @@ export default class MeasurementApi {
|
||||
this.options.onMeasurementsUpdated(Object.assign({}, this.tools));
|
||||
}
|
||||
|
||||
retrieveMeasurements(patientId, timepointIds) {
|
||||
retrieveMeasurements(PatientID, timepointIds) {
|
||||
const retrievalFn = configuration.dataExchange.retrieve;
|
||||
const { server } = configuration;
|
||||
if (typeof retrievalFn !== 'function') {
|
||||
@@ -297,9 +296,9 @@ export default class MeasurementApi {
|
||||
: null;
|
||||
const timepoints = this.timepointApi.all(timepointFilter);
|
||||
const timepointIds = timepoints.map(t => t.timepointId);
|
||||
const patientId = timepoints[0].patientId;
|
||||
const PatientID = timepoints[0].PatientID;
|
||||
const filter = {
|
||||
patientId,
|
||||
PatientID,
|
||||
timepointIds,
|
||||
};
|
||||
|
||||
@@ -576,17 +575,17 @@ export default class MeasurementApi {
|
||||
|
||||
const toolGroupId = this.toolsGroupsMap[measurementData.toolType];
|
||||
|
||||
// TODO: Remove TrialPatientLocationUid from here and override it somehow
|
||||
// TODO: Remove TrialPatientLocationUID from here and override it somehow
|
||||
// by dependant applications. Here we should use the location attribute instead of the uid
|
||||
let filter;
|
||||
const uid =
|
||||
measurementData.additionalData &&
|
||||
measurementData.additionalData.TrialPatientLocationUid;
|
||||
measurementData.additionalData.TrialPatientLocationUID;
|
||||
if (uid) {
|
||||
filter = tool =>
|
||||
tool._id !== measurementData._id &&
|
||||
tool.additionalData &&
|
||||
tool.additionalData.TrialPatientLocationUid === uid;
|
||||
tool.additionalData.TrialPatientLocationUID === uid;
|
||||
} else {
|
||||
filter = tool =>
|
||||
tool._id !== measurementData._id &&
|
||||
@@ -691,8 +690,8 @@ export default class MeasurementApi {
|
||||
|
||||
// Get the timepoint
|
||||
let timepoint;
|
||||
if (measurement.studyInstanceUid) {
|
||||
timepoint = this.timepointApi.study(measurement.studyInstanceUid)[0];
|
||||
if (measurement.StudyInstanceUID) {
|
||||
timepoint = this.timepointApi.study(measurement.StudyInstanceUID)[0];
|
||||
} else {
|
||||
const { timepointId } = measurement;
|
||||
timepoint = this.timepointApi.timepoints.find(
|
||||
@@ -760,7 +759,7 @@ export default class MeasurementApi {
|
||||
measurement.lesionNamingNumber = found.lesionNamingNumber;
|
||||
measurement.measurementNumber = found.measurementNumber;
|
||||
|
||||
// TODO: Remove TrialPatientLocationUid from here and override it somehow
|
||||
// TODO: Remove TrialPatientLocationUID from here and override it somehow
|
||||
// by dependant applications
|
||||
|
||||
// Change the update object to set the same number, additionalData,
|
||||
@@ -768,8 +767,8 @@ export default class MeasurementApi {
|
||||
updateObject.lesionNamingNumber = found.lesionNamingNumber;
|
||||
updateObject.measurementNumber = found.measurementNumber;
|
||||
updateObject.additionalData = measurement.additionalData || {};
|
||||
updateObject.additionalData.TrialPatientLocationUid =
|
||||
found.additionalData && found.additionalData.TrialPatientLocationUid;
|
||||
updateObject.additionalData.TrialPatientLocationUID =
|
||||
found.additionalData && found.additionalData.TrialPatientLocationUID;
|
||||
updateObject.location = found.location;
|
||||
updateObject.label = found.label;
|
||||
updateObject.description = found.description;
|
||||
@@ -807,7 +806,7 @@ export default class MeasurementApi {
|
||||
toolId: toolType,
|
||||
toolItemId: addedMeasurement._id,
|
||||
timepointId: timepoint.timepointId,
|
||||
studyInstanceUid: addedMeasurement.studyInstanceUid,
|
||||
StudyInstanceUID: addedMeasurement.StudyInstanceUID,
|
||||
createdAt: addedMeasurement.createdAt,
|
||||
lesionNamingNumber: addedMeasurement.lesionNamingNumber,
|
||||
measurementNumber: addedMeasurement.measurementNumber,
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class TimepointApi {
|
||||
});
|
||||
const filteredTimepoints = sortedTimepoints.find(
|
||||
tp =>
|
||||
tp.patientId === timepoint.patientId &&
|
||||
tp.PatientID === timepoint.PatientID &&
|
||||
tp.timepointType === timepoint.timepointType
|
||||
);
|
||||
|
||||
@@ -129,14 +129,14 @@ export default class TimepointApi {
|
||||
);
|
||||
}
|
||||
|
||||
disassociateStudy(timepointIds, studyInstanceUid) {
|
||||
disassociateStudy(timepointIds, StudyInstanceUID) {
|
||||
const disassociateFn = configuration.dataExchange.disassociate;
|
||||
if (typeof disassociateFn !== 'function') {
|
||||
log.error('Study disassociate function has not been configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
disassociateFn(timepointIds, studyInstanceUid).then(() => {
|
||||
disassociateFn(timepointIds, StudyInstanceUID).then(() => {
|
||||
log.info('Disassociation completed');
|
||||
|
||||
this.timepoints = [];
|
||||
@@ -510,9 +510,9 @@ export default class TimepointApi {
|
||||
}
|
||||
|
||||
// Return only the timepoints for the given study
|
||||
study(studyInstanceUid) {
|
||||
study(StudyInstanceUID) {
|
||||
return this.all().filter(timepoint =>
|
||||
timepoint.studyInstanceUids.includes(studyInstanceUid)
|
||||
timepoint.studyInstanceUIDs.includes(StudyInstanceUID)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 314 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user