Compare commits
42
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 |
No files matched your search
Vendored
+4
-1
@@ -28,5 +28,8 @@
|
|||||||
],
|
],
|
||||||
"prettier.disableLanguages": ["html"],
|
"prettier.disableLanguages": ["html"],
|
||||||
"prettier.endOfLine": "lf",
|
"prettier.endOfLine": "lf",
|
||||||
"workbench.colorCustomizations": {}
|
"workbench.colorCustomizations": {},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,10 @@ export default {
|
|||||||
from: 'right',
|
from: 'right',
|
||||||
// The target component to toggle open/close
|
// The target component to toggle open/close
|
||||||
target: 'target-component-id',
|
target: 'target-component-id',
|
||||||
|
// UI Hint; If the target panel is in a "disabled" state
|
||||||
|
isDisabled: studies => {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
// Overrides `defaultContext`, if specified
|
// Overrides `defaultContext`, if specified
|
||||||
context: ['ACTIVE_VIEWPORT:MAGIC'],
|
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
|
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
|
||||||
own components. It instead defines:
|
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` method should be applied to.
|
||||||
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
||||||
display set
|
display set
|
||||||
@@ -15,13 +15,13 @@ A `displaySet` has the following shape:
|
|||||||
```js
|
```js
|
||||||
return {
|
return {
|
||||||
plugin: 'html',
|
plugin: 'html',
|
||||||
modality: 'SR',
|
Modality: 'SR',
|
||||||
displaySetInstanceUid: 0,
|
displaySetInstanceUID: 0,
|
||||||
wadoRoot: study.getData().wadoRoot,
|
wadoRoot: study.getData().wadoRoot,
|
||||||
wadoUri: instance.getData().wadouri,
|
wadoUri: instance.getData().wadouri,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -50,7 +50,7 @@ export default {
|
|||||||
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||||
return {
|
return {
|
||||||
id: 'OHIFDicomHtmlSopClassHandler',
|
id: 'OHIFDicomHtmlSopClassHandler',
|
||||||
sopClassUids: Object.values(SOP_CLASS_UIDS),
|
sopClassUIDs: Object.values(SOP_CLASS_UIDS),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} series -
|
* @param {object} series -
|
||||||
@@ -63,12 +63,12 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'html',
|
plugin: 'html',
|
||||||
displaySetInstanceUid: 0,
|
displaySetInstanceUID: 0,
|
||||||
wadoRoot: study.getData().wadoRoot,
|
wadoRoot: study.getData().wadoRoot,
|
||||||
wadoUri: instance.getData().wadouri,
|
wadoUri: instance.getData().wadouri,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Lesion Tracker - Audit Trail
|
# Lesion Tracker - Audit Trail
|
||||||
|
|
||||||
1. To view Audit Trails, select **View Audit Log** on the Configuration menu.
|
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.
|
1. Study List allows to sort each column. To sort studies, click the desired
|
||||||
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.
|
column header.
|
||||||
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.
|
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
|
## 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:
|
There are a couple of ways to view a study:
|
||||||
|
|
||||||
* Simply double-click on the study row.
|
- Simply double-click on the study row.
|
||||||
* Select **View** option on the Study Context Menu, then you will be redirected to Viewer page.
|
- Select **View** option on the Study Context Menu, then you will be redirected
|
||||||
|
to Viewer page.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Timepoint Association
|
### 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.
|
1. To link a study with a timepoint, right-click on the study row and select
|
||||||
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
|
**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
|
### 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
|
### 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)
|
### Anonymize (Under Development)
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
### Send (Under Development)
|
### Send (Under Development)
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
### Export (Under Development)
|
### Export (Under Development)
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
### Delete (Under Development)
|
### Delete (Under Development)
|
||||||
|
|
||||||
...
|
...
|
||||||
@@ -21,7 +21,6 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* MODULE GETTERS
|
* MODULE GETTERS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return '... react component ...';
|
return '... react component ...';
|
||||||
},
|
},
|
||||||
@@ -64,7 +63,7 @@ const commandsModule = {
|
|||||||
*/
|
*/
|
||||||
const sopClassHandlerModule = {
|
const sopClassHandlerModule = {
|
||||||
id: 'OHIFDicomHtmlSopClassHandler',
|
id: 'OHIFDicomHtmlSopClassHandler',
|
||||||
sopClassUids: Object.values({
|
sopClassUIDs: Object.values({
|
||||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||||
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||||
@@ -78,12 +77,12 @@ const sopClassHandlerModule = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'html',
|
plugin: 'html',
|
||||||
displaySetInstanceUid: 0, //utils.guid(),
|
displaySetInstanceUID: 0, //utils.guid(),
|
||||||
wadoRoot: study.getData().wadoRoot,
|
wadoRoot: study.getData().wadoRoot,
|
||||||
wadoUri: instance.getData().wadouri,
|
wadoUri: instance.getData().wadouri,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -98,6 +97,9 @@ const panelModule = {
|
|||||||
icon: 'th-list',
|
icon: 'th-list',
|
||||||
label: 'Segments',
|
label: 'Segments',
|
||||||
target: 'segment-panel',
|
target: 'segment-panel',
|
||||||
|
isDisabled: studies => {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
components: [
|
components: [
|
||||||
|
|||||||
@@ -3,6 +3,58 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.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)
|
# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.2...@ohif/extension-cornerstone@2.3.0) (2020-02-10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.3.0",
|
"version": "2.4.3",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
"cornerstone-tools": "^4.12.0",
|
"cornerstone-tools": "4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.8.2",
|
"dcmjs": "^0.10.1",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"query-string": "^6.8.3",
|
"react-cornerstone-viewport": "^2.3.6"
|
||||||
"react-cornerstone-viewport": "2.x.x"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,15 +8,6 @@ import handleSegmentationStorage from './handleSegmentationStorage.js';
|
|||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
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 = {
|
const SOP_CLASSES = {
|
||||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
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.
|
* Obtain the CornerstoneTools Stack for the specified display set.
|
||||||
*
|
*
|
||||||
* @param {Object[]} studies
|
* @param {Object[]} studies
|
||||||
* @param {String} studyInstanceUid
|
* @param {String} StudyInstanceUID
|
||||||
* @param {String} displaySetInstanceUid
|
* @param {String} displaySetInstanceUID
|
||||||
* @param {String} [sopInstanceUid]
|
* @param {String} [SOPInstanceUID]
|
||||||
* @param {Number} [frameIndex=1]
|
* @param {Number} [frameIndex=1]
|
||||||
* @return {Object} CornerstoneTools Stack
|
* @return {Object} CornerstoneTools Stack
|
||||||
*/
|
*/
|
||||||
static getCornerstoneStack(
|
static getCornerstoneStack(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex = 0
|
frameIndex = 0
|
||||||
) {
|
) {
|
||||||
if (!studies || !studies.length) {
|
if (!studies || !studies.length) {
|
||||||
throw new Error('Studies not provided.');
|
throw new Error('Studies not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!studyInstanceUid) {
|
if (!StudyInstanceUID) {
|
||||||
throw new Error('StudyInstanceUID not provided.');
|
throw new Error('StudyInstanceUID not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!displaySetInstanceUid) {
|
if (!displaySetInstanceUID) {
|
||||||
throw new Error('StudyInstanceUID not provided.');
|
throw new Error('StudyInstanceUID not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create shortcut to displaySet
|
// Create shortcut to displaySet
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!study) {
|
if (!study) {
|
||||||
@@ -93,7 +84,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
const displaySet = study.displaySets.find(set => {
|
||||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!displaySet) {
|
if (!displaySet) {
|
||||||
@@ -107,17 +98,14 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
const stack = Object.assign({}, storedStack);
|
const stack = Object.assign({}, storedStack);
|
||||||
stack.currentImageIdIndex = frameIndex;
|
stack.currentImageIdIndex = frameIndex;
|
||||||
|
|
||||||
if (sopInstanceUid) {
|
if (SOPInstanceUID) {
|
||||||
const index = stack.imageIds.findIndex(imageId => {
|
const index = stack.imageIds.findIndex(imageId => {
|
||||||
const sopCommonModule = cornerstone.metaData.get(
|
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||||
'sopCommonModule',
|
'SOPInstanceUID',
|
||||||
imageId
|
imageId
|
||||||
);
|
);
|
||||||
if (!sopCommonModule) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -134,39 +122,39 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
|
|
||||||
getViewportData = async (
|
getViewportData = async (
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUid,
|
SOPClassUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
) => {
|
) => {
|
||||||
let viewportData;
|
let viewportData;
|
||||||
|
|
||||||
switch (sopClassUid) {
|
switch (SOPClassUID) {
|
||||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||||
const specialCaseHandler =
|
const specialCaseHandler =
|
||||||
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
||||||
|
|
||||||
viewportData = await specialCaseHandler(
|
viewportData = await specialCaseHandler(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
viewportData = {
|
viewportData = {
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
stack,
|
stack,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,31 +167,31 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
setStateFromProps() {
|
setStateFromProps() {
|
||||||
const { studies, displaySet } = this.props.viewportData;
|
const { studies, displaySet } = this.props.viewportData;
|
||||||
const {
|
const {
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
} = displaySet;
|
} = displaySet;
|
||||||
|
|
||||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sopClassUids && sopClassUids.length > 1) {
|
if (sopClassUIDs && sopClassUIDs.length > 1) {
|
||||||
console.warn(
|
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(
|
this.getViewportData(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUid,
|
SOPClassUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
).then(viewportData => {
|
).then(viewportData => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -221,9 +209,9 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
displaySet.displaySetInstanceUid !==
|
displaySet.displaySetInstanceUID !==
|
||||||
prevDisplaySet.displaySetInstanceUid ||
|
prevDisplaySet.displaySetInstanceUID ||
|
||||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
|
|||||||
@@ -74,10 +74,6 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
}
|
}
|
||||||
cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 });
|
cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 });
|
||||||
},
|
},
|
||||||
updateViewportDisplaySet: ({ direction }) => {
|
|
||||||
// TODO
|
|
||||||
console.warn('updateDisplaySet: ', direction);
|
|
||||||
},
|
|
||||||
clearAnnotations: ({ viewports }) => {
|
clearAnnotations: ({ viewports }) => {
|
||||||
const element = getEnabledElement(viewports.activeViewportIndex);
|
const element = getEnabledElement(viewports.activeViewportIndex);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -245,7 +241,20 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
},
|
},
|
||||||
setCornerstoneLayout: () => {
|
setCornerstoneLayout: () => {
|
||||||
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 = {
|
const definitions = {
|
||||||
@@ -334,30 +343,28 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
storeContexts: ['viewports'],
|
storeContexts: ['viewports'],
|
||||||
options: {},
|
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
|
// TOOLS
|
||||||
setToolActive: {
|
setToolActive: {
|
||||||
commandFn: actions.setToolActive,
|
commandFn: actions.setToolActive,
|
||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
setZoomTool: {
|
||||||
|
commandFn: actions.setToolActive,
|
||||||
|
storeContexts: [],
|
||||||
|
options: { toolName: 'Zoom' },
|
||||||
|
},
|
||||||
setCornerstoneLayout: {
|
setCornerstoneLayout: {
|
||||||
commandFn: actions.setCornerstoneLayout,
|
commandFn: actions.setCornerstoneLayout,
|
||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
context: 'VIEWER',
|
context: 'VIEWER',
|
||||||
},
|
},
|
||||||
|
setWindowLevel: {
|
||||||
|
commandFn: actions.setWindowLevel,
|
||||||
|
storeContexts: ['viewports'],
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,25 +6,25 @@ import cornerstoneTools from 'cornerstone-tools';
|
|||||||
|
|
||||||
const { StackManager, DicomLoaderService } = OHIF.utils;
|
const { StackManager, DicomLoaderService } = OHIF.utils;
|
||||||
|
|
||||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
const displaySet = study.displaySets.find(set => {
|
||||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
return displaySet;
|
return displaySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return study.displaySets.filter(set => {
|
return study.displaySets.filter(set => {
|
||||||
return set.seriesInstanceUid === seriesInstanceUid;
|
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ function parseSeg(arrayBuffer, imageIds) {
|
|||||||
function addSegMetadataToCornerstoneToolState(
|
function addSegMetadataToCornerstoneToolState(
|
||||||
segMetadata,
|
segMetadata,
|
||||||
toolState,
|
toolState,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||||
toolState
|
toolState
|
||||||
@@ -48,22 +48,22 @@ function addSegMetadataToCornerstoneToolState(
|
|||||||
const brushModule = cornerstoneTools.store.modules.brush;
|
const brushModule = cornerstoneTools.store.modules.brush;
|
||||||
|
|
||||||
for (let i = 0; i < segMetadata.length; i++) {
|
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(
|
async function handleSegmentationStorage(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
const displaySet = getDisplaySet(
|
const displaySet = getDisplaySet(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||||
@@ -77,16 +77,16 @@ async function handleSegmentationStorage(
|
|||||||
|
|
||||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||||
|
|
||||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||||
const displaySets = getDisplaySetsBySeries(
|
const displaySets = getDisplaySetsBySeries(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid
|
SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (displaySets.length > 1) {
|
if (displaySets.length > 1) {
|
||||||
console.warn(
|
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;
|
stack.currentImageIdIndex = 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
stack,
|
stack,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,9 @@ import { SimpleDialog } from '@ohif/ui';
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import csTools from 'cornerstone-tools';
|
import csTools from 'cornerstone-tools';
|
||||||
import merge from 'lodash.merge';
|
import merge from 'lodash.merge';
|
||||||
import queryString from 'query-string';
|
|
||||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} servicesManager
|
* @param {Object} servicesManager
|
||||||
@@ -58,16 +38,13 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { csToolsConfig } = configuration;
|
const { csToolsConfig } = configuration;
|
||||||
const { StackManager } = OHIF.utils;
|
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
|
||||||
|
|
||||||
// ~~ Set our MetadataProvider
|
|
||||||
cornerstone.metaData.addProvider(
|
cornerstone.metaData.addProvider(
|
||||||
metadataProvider.provider.bind(metadataProvider)
|
metadataProvider.get.bind(metadataProvider),
|
||||||
|
9999
|
||||||
);
|
);
|
||||||
|
|
||||||
StackManager.setMetadataProvider(metadataProvider);
|
|
||||||
|
|
||||||
// ~~
|
// ~~
|
||||||
const defaultCsToolsConfig = csToolsConfig || {
|
const defaultCsToolsConfig = csToolsConfig || {
|
||||||
globalToolSyncEnabled: true,
|
globalToolSyncEnabled: true,
|
||||||
@@ -81,7 +58,6 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
||||||
annotations: [
|
annotations: [
|
||||||
csTools.ArrowAnnotateTool,
|
csTools.ArrowAnnotateTool,
|
||||||
csTools.EraserTool,
|
|
||||||
csTools.BidirectionalTool,
|
csTools.BidirectionalTool,
|
||||||
csTools.LengthTool,
|
csTools.LengthTool,
|
||||||
csTools.AngleTool,
|
csTools.AngleTool,
|
||||||
@@ -189,7 +165,9 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
|
|
||||||
const _initMeasurementService = measurementService => {
|
const _initMeasurementService = measurementService => {
|
||||||
/* Initialization */
|
/* Initialization */
|
||||||
const { toAnnotation, toMeasurement } = measurementServiceMappingsFactory(measurementService);
|
const { toAnnotation, toMeasurement } = measurementServiceMappingsFactory(
|
||||||
|
measurementService
|
||||||
|
);
|
||||||
const csToolsVer4MeasurementSource = measurementService.createSource(
|
const csToolsVer4MeasurementSource = measurementService.createSource(
|
||||||
'CornerstoneTools',
|
'CornerstoneTools',
|
||||||
'4'
|
'4'
|
||||||
@@ -214,7 +192,9 @@ const _initMeasurementService = measurementService => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _connectToolsToMeasurementService = measurementService => {
|
const _connectToolsToMeasurementService = measurementService => {
|
||||||
const csToolsVer4MeasurementSource = _initMeasurementService(measurementService);
|
const csToolsVer4MeasurementSource = _initMeasurementService(
|
||||||
|
measurementService
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
id: sourceId,
|
id: sourceId,
|
||||||
addOrUpdate,
|
addOrUpdate,
|
||||||
@@ -242,7 +222,8 @@ const _connectToolsToMeasurementService = measurementService => {
|
|||||||
);
|
);
|
||||||
console.log('Mapped annotation:', annotation);
|
console.log('Mapped annotation:', annotation);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
measurementService.subscribe(
|
measurementService.subscribe(
|
||||||
MEASUREMENT_UPDATED,
|
MEASUREMENT_UPDATED,
|
||||||
|
|||||||
@@ -215,15 +215,6 @@ const definitions = [
|
|||||||
commandName: 'setToolActive',
|
commandName: 'setToolActive',
|
||||||
commandOptions: { toolName: 'Bidirectional' },
|
commandOptions: { toolName: 'Bidirectional' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'Eraser',
|
|
||||||
label: 'Eraser',
|
|
||||||
icon: 'eraser',
|
|
||||||
//
|
|
||||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
|
||||||
commandName: 'setToolActive',
|
|
||||||
commandOptions: { toolName: 'Eraser' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'Download',
|
id: 'Download',
|
||||||
label: 'Download',
|
label: 'Download',
|
||||||
@@ -245,7 +236,7 @@ const definitions = [
|
|||||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
commandName: 'setCornerstoneLayout',
|
commandName: 'setCornerstoneLayout',
|
||||||
context: 'ACTIVE_VIEWPORT::VTK',
|
context: 'ACTIVE_VIEWPORT::VTK',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
+27
-19
@@ -1,6 +1,11 @@
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
const SUPPORTED_TOOLS = ['Length', 'EllipticalRoi', 'RectangleRoi', 'ArrowAnnotate'];
|
const SUPPORTED_TOOLS = [
|
||||||
|
'Length',
|
||||||
|
'EllipticalRoi',
|
||||||
|
'RectangleRoi',
|
||||||
|
'ArrowAnnotate',
|
||||||
|
];
|
||||||
|
|
||||||
const measurementServiceMappingsFactory = measurementService => {
|
const measurementServiceMappingsFactory = measurementService => {
|
||||||
/**
|
/**
|
||||||
@@ -17,17 +22,17 @@ const measurementServiceMappingsFactory = measurementService => {
|
|||||||
description,
|
description,
|
||||||
points,
|
points,
|
||||||
unit,
|
unit,
|
||||||
sopInstanceUID,
|
SOPInstanceUID,
|
||||||
frameOfReferenceUID,
|
FrameOfReferenceUID,
|
||||||
referenceSeriesUID,
|
referenceSeriesUID,
|
||||||
} = measurement;
|
} = measurement;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolName: definition,
|
toolName: definition,
|
||||||
measurementData: {
|
measurementData: {
|
||||||
sopInstanceUid: sopInstanceUID,
|
sopInstanceUid: SOPInstanceUID,
|
||||||
frameOfReferenceUid: frameOfReferenceUID,
|
frameOfReferenceUID: FrameOfReferenceUID,
|
||||||
seriesInstanceUid: referenceSeriesUID,
|
SeriesInstanceUID: referenceSeriesUID,
|
||||||
unit,
|
unit,
|
||||||
text: label,
|
text: label,
|
||||||
description,
|
description,
|
||||||
@@ -57,9 +62,9 @@ const measurementServiceMappingsFactory = measurementService => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameOfReferenceUid,
|
FrameOfReferenceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
} = _getAttributes(element);
|
} = _getAttributes(element);
|
||||||
|
|
||||||
const points = [];
|
const points = [];
|
||||||
@@ -67,13 +72,16 @@ const measurementServiceMappingsFactory = measurementService => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: measurementData._measurementServiceId,
|
id: measurementData._measurementServiceId,
|
||||||
sopInstanceUID: sopInstanceUid,
|
SOPInstanceUID: SOPInstanceUID,
|
||||||
frameOfReferenceUID: frameOfReferenceUid,
|
FrameOfReferenceUID,
|
||||||
referenceSeriesUID: seriesInstanceUid,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
label: measurementData.text,
|
label: measurementData.text,
|
||||||
description: measurementData.description,
|
description: measurementData.description,
|
||||||
unit: measurementData.unit,
|
unit: measurementData.unit,
|
||||||
area: measurementData.cachedStats && measurementData.cachedStats.area, /* TODO: Add concept names instead (descriptor) */
|
area:
|
||||||
|
measurementData.cachedStats &&
|
||||||
|
measurementData.cachedStats
|
||||||
|
.area /* TODO: Add concept names instead (descriptor) */,
|
||||||
type: _getValueTypeFromToolType(tool),
|
type: _getValueTypeFromToolType(tool),
|
||||||
points: _getPointsFromHandles(measurementData.handles),
|
points: _getPointsFromHandles(measurementData.handles),
|
||||||
};
|
};
|
||||||
@@ -82,13 +90,13 @@ const measurementServiceMappingsFactory = measurementService => {
|
|||||||
const _getAttributes = element => {
|
const _getAttributes = element => {
|
||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
const imageId = enabledElement.image.imageId;
|
const imageId = enabledElement.image.imageId;
|
||||||
const sopInstance = cornerstone.metaData.get('instance', imageId);
|
const instance = cornerstone.metaData.get('instance', imageId);
|
||||||
const sopInstanceUid = sopInstance.sopInstanceUid;
|
|
||||||
const frameOfReferenceUid = sopInstance.frameOfReferenceUID;
|
|
||||||
const series = cornerstone.metaData.get('series', imageId);
|
|
||||||
const seriesInstanceUid = series.seriesInstanceUid;
|
|
||||||
|
|
||||||
return { sopInstanceUid, frameOfReferenceUid, seriesInstanceUid };
|
return {
|
||||||
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
|
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getValueTypeFromToolType = toolType => {
|
const _getValueTypeFromToolType = toolType => {
|
||||||
|
|||||||
+11
-8
@@ -8,9 +8,9 @@ jest.mock('cornerstone-core', () => ({
|
|||||||
metaData: {
|
metaData: {
|
||||||
...jest.requireActual('cornerstone-core').metaData,
|
...jest.requireActual('cornerstone-core').metaData,
|
||||||
get: () => ({
|
get: () => ({
|
||||||
sopInstanceUid: "123",
|
SOPInstanceUID: '123',
|
||||||
frameOfReferenceUID: "123",
|
FrameOfReferenceUID: '123',
|
||||||
seriesInstanceUid: "123",
|
SeriesInstanceUID: '123',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -42,8 +42,8 @@ describe('measurementServiceMappings.js', () => {
|
|||||||
measurementData: {
|
measurementData: {
|
||||||
_measurementServiceId: 1,
|
_measurementServiceId: 1,
|
||||||
sopInstanceUid: '123',
|
sopInstanceUid: '123',
|
||||||
frameOfReferenceUid: '123',
|
frameOfReferenceUID: '123',
|
||||||
seriesInstanceUid: '123',
|
SeriesInstanceUID: '123',
|
||||||
handles,
|
handles,
|
||||||
text: 'Test',
|
text: 'Test',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
@@ -52,8 +52,8 @@ describe('measurementServiceMappings.js', () => {
|
|||||||
};
|
};
|
||||||
measurement = {
|
measurement = {
|
||||||
id: 1,
|
id: 1,
|
||||||
sopInstanceUID: '123',
|
SOPInstanceUID: '123',
|
||||||
frameOfReferenceUID: '123',
|
FrameOfReferenceUID: '123',
|
||||||
referenceSeriesUID: '123',
|
referenceSeriesUID: '123',
|
||||||
label: 'Test',
|
label: 'Test',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
@@ -66,7 +66,10 @@ describe('measurementServiceMappings.js', () => {
|
|||||||
|
|
||||||
describe('toAnnotation()', () => {
|
describe('toAnnotation()', () => {
|
||||||
it('map measurement service format to annotation', async () => {
|
it('map measurement service format to annotation', async () => {
|
||||||
const mappedMeasurement = await mappings.toAnnotation(measurement, definition);
|
const mappedMeasurement = await mappings.toAnnotation(
|
||||||
|
measurement,
|
||||||
|
definition
|
||||||
|
);
|
||||||
expect(mappedMeasurement).toEqual(csToolsAnnotation);
|
expect(mappedMeasurement).toEqual(csToolsAnnotation);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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.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",
|
"name": "@ohif/extension-dicom-html",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "OHIF extension for rendering structured reports to HTML",
|
"description": "OHIF extension for rendering structured reports to HTML",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^0.50.0",
|
"@ohif/core": "^0.50.0",
|
||||||
"dcmjs": "^0.8.2",
|
"dcmjs": "^0.10.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6"
|
"react-dom": "^16.8.6"
|
||||||
|
|||||||
@@ -11,26 +11,26 @@ const SOP_CLASS_UIDS = {
|
|||||||
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||||
// same SOP Class
|
// same SOP Class
|
||||||
const OHIFDicomHtmlSopClassHandler = {
|
const OHIFDicomHtmlSopClassHandler = {
|
||||||
id: 'OHIFDicomHtmlSopClassHandler',
|
id: 'OHIFDicomHtmlSopClassHandler',
|
||||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'html',
|
plugin: 'html',
|
||||||
modality: 'SR',
|
Modality: 'SR',
|
||||||
displaySetInstanceUid: utils.guid(),
|
displaySetInstanceUID: utils.guid(),
|
||||||
wadoRoot: study.getData().wadoRoot,
|
wadoRoot: study.getData().wadoRoot,
|
||||||
wadoUri: instance.getData().wadouri,
|
wadoUri: instance.getData().wadouri,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.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)
|
## [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",
|
"name": "@ohif/extension-dicom-microscopy",
|
||||||
"version": "0.50.6",
|
"version": "0.50.7",
|
||||||
"description": "OHIF extension for Dicom Microscopy",
|
"description": "OHIF extension for Dicom Microscopy",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const SOP_CLASS_UIDS = {
|
|||||||
|
|
||||||
const DicomMicroscopySopClassHandler = {
|
const DicomMicroscopySopClassHandler = {
|
||||||
id: 'DicomMicroscopySopClassHandlerPlugin',
|
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) {
|
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ const DicomMicroscopySopClassHandler = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'microscopy',
|
plugin: 'microscopy',
|
||||||
modality: 'SM',
|
Modality: 'SM',
|
||||||
displaySetInstanceUid: utils.guid(),
|
displaySetInstanceUID: utils.guid(),
|
||||||
dicomWebClient,
|
dicomWebClient,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
const dicomWebClient = displaySet.dicomWebClient;
|
const dicomWebClient = displaySet.dicomWebClient;
|
||||||
|
|
||||||
const searchInstanceOptions = {
|
const searchInstanceOptions = {
|
||||||
studyInstanceUID: displaySet.studyInstanceUid,
|
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
dicomWebClient
|
dicomWebClient
|
||||||
@@ -34,18 +34,18 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
.then(instances => {
|
.then(instances => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < instances.length; i++) {
|
for (let i = 0; i < instances.length; i++) {
|
||||||
const sopInstanceUID = instances[i]['00080018']['Value'][0];
|
const sopInstanceUid = instances[i]['00080018']['Value'][0];
|
||||||
const retrieveInstanceOptions = {
|
const retrieveInstanceOptions = {
|
||||||
studyInstanceUID: displaySet.studyInstanceUid,
|
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||||
sopInstanceUID,
|
sopInstanceUid,
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = dicomWebClient
|
const promise = dicomWebClient
|
||||||
.retrieveInstanceMetadata(retrieveInstanceOptions)
|
.retrieveInstanceMetadata(retrieveInstanceOptions)
|
||||||
.then(metadata => {
|
.then(metadata => {
|
||||||
const imageType = metadata[0]['00080008']['Value'];
|
const ImageType = metadata[0]['00080008']['Value'];
|
||||||
if (imageType[2] === 'VOLUME') {
|
if (ImageType[2] === 'VOLUME') {
|
||||||
return metadata[0];
|
return metadata[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -56,13 +56,15 @@ class DicomMicroscopyViewport extends Component {
|
|||||||
.then(async metadata => {
|
.then(async metadata => {
|
||||||
metadata = metadata.filter(m => m);
|
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;
|
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
|
||||||
|
|
||||||
this.viewer = new microscopyViewer({
|
this.viewer = new microscopyViewer({
|
||||||
client: dicomWebClient,
|
client: dicomWebClient,
|
||||||
metadata,
|
metadata,
|
||||||
retrieveRendered: false
|
retrieveRendered: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.viewer.render({ container });
|
this.viewer.render({ container });
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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.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",
|
"name": "@ohif/extension-dicom-pdf",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "OHIF extension for Dicom PDF",
|
"description": "OHIF extension for Dicom PDF",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -101,9 +101,9 @@ class DicomPDFViewport extends Component {
|
|||||||
let pdfByteArray = byteArray;
|
let pdfByteArray = byteArray;
|
||||||
|
|
||||||
if (dataSet) {
|
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');
|
throw new Error('This is not a DICOM-encapsulated PDF');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ const SOP_CLASS_UIDS = {
|
|||||||
const OHIFDicomPDFSopClassHandler = {
|
const OHIFDicomPDFSopClassHandler = {
|
||||||
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
||||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||||
sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
sopClassUIDs: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
||||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugin: 'pdf',
|
plugin: 'pdf',
|
||||||
modality: 'DOC',
|
Modality: 'DOC',
|
||||||
displaySetInstanceUid: utils.guid(),
|
displaySetInstanceUID: utils.guid(),
|
||||||
wadoRoot: study.getData().wadoRoot,
|
wadoRoot: study.getData().wadoRoot,
|
||||||
wadoUri: instance.getData().wadouri,
|
wadoUri: instance.getData().wadouri,
|
||||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
authorizationHeaders: authorizationHeaders,
|
authorizationHeaders: authorizationHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class OHIFDicomPDFViewport extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { displaySet, studies } = this.props.viewportData;
|
const { displaySet, studies } = this.props.viewportData;
|
||||||
|
|
||||||
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||||
data => this.setState({ byteArray: new Uint8Array(data) }),
|
data => this.setState({ byteArray: new Uint8Array(data) }),
|
||||||
error => {
|
error => {
|
||||||
|
|||||||
@@ -3,6 +3,62 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.2.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)
|
## [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
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.2.17",
|
"version": "1.2.24",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"@ohif/ui": "^0.50.0",
|
"@ohif/ui": "^0.50.0",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.8.2",
|
"dcmjs": "^0.10.1",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"i18next": "^17.0.3",
|
"i18next": "^17.0.3",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
@@ -52,9 +52,9 @@
|
|||||||
"react-vtkjs-viewport": "^0.8.3"
|
"react-vtkjs-viewport": "^0.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^2.4.0",
|
"@ohif/core": "^2.5.3",
|
||||||
"@ohif/ui": "^1.2.0",
|
"@ohif/ui": "^1.3.3",
|
||||||
"cornerstone-tools": "^4.12.0",
|
"cornerstone-tools": "4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dicom-parser": "^1.8.3",
|
"dicom-parser": "^1.8.3",
|
||||||
"gh-pages": "^2.0.1",
|
"gh-pages": "^2.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
|
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
|
||||||
|
|
||||||
import ConnectedVTKViewport from './ConnectedVTKViewport';
|
import ConnectedVTKViewport from './ConnectedVTKViewport';
|
||||||
import LoadingIndicator from './LoadingIndicator.js';
|
import LoadingIndicator from './LoadingIndicator.js';
|
||||||
import OHIF from '@ohif/core';
|
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';
|
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||||
// Metadata configuration
|
|
||||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
|
||||||
|
|
||||||
cornerstone.metaData.addProvider(
|
|
||||||
metadataProvider.provider.bind(metadataProvider)
|
|
||||||
);
|
|
||||||
|
|
||||||
StackManager.setMetadataProvider(metadataProvider);
|
|
||||||
|
|
||||||
const SOP_CLASSES = {
|
const SOP_CLASSES = {
|
||||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||||
@@ -68,10 +59,10 @@ class OHIFVTKViewport extends Component {
|
|||||||
viewportData: PropTypes.shape({
|
viewportData: PropTypes.shape({
|
||||||
studies: PropTypes.array,
|
studies: PropTypes.array,
|
||||||
displaySet: PropTypes.shape({
|
displaySet: PropTypes.shape({
|
||||||
studyInstanceUid: PropTypes.string,
|
StudyInstanceUID: PropTypes.string,
|
||||||
displaySetInstanceUid: PropTypes.string,
|
displaySetInstanceUID: PropTypes.string,
|
||||||
sopClassUids: PropTypes.arrayOf(PropTypes.string),
|
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
||||||
sopInstanceUid: PropTypes.string,
|
SOPInstanceUID: PropTypes.string,
|
||||||
frameIndex: PropTypes.number,
|
frameIndex: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -97,18 +88,18 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
static getCornerstoneStack(
|
static getCornerstoneStack(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
) {
|
) {
|
||||||
// Create shortcut to displaySet
|
// Create shortcut to displaySet
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
const displaySet = study.displaySets.find(set => {
|
||||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get stack from Stack Manager
|
// Get stack from Stack Manager
|
||||||
@@ -119,17 +110,14 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
if (frameIndex !== undefined) {
|
if (frameIndex !== undefined) {
|
||||||
stack.currentImageIdIndex = frameIndex;
|
stack.currentImageIdIndex = frameIndex;
|
||||||
} else if (sopInstanceUid) {
|
} else if (SOPInstanceUID) {
|
||||||
const index = stack.imageIds.findIndex(imageId => {
|
const index = stack.imageIds.findIndex(imageId => {
|
||||||
const sopCommonModule = cornerstone.metaData.get(
|
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||||
'sopCommonModule',
|
'SOPInstanceUID',
|
||||||
imageId
|
imageId
|
||||||
);
|
);
|
||||||
if (!sopCommonModule) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -144,31 +132,31 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
getViewportData = (
|
getViewportData = (
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUid,
|
SOPClassUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
) => {
|
) => {
|
||||||
const stack = OHIFVTKViewport.getCornerstoneStack(
|
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUid,
|
SOPClassUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
let imageDataObject;
|
let imageDataObject;
|
||||||
let labelmapDataObject;
|
let labelmapDataObject;
|
||||||
|
|
||||||
switch (sopClassUid) {
|
switch (SOPClassUID) {
|
||||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||||
throw new Error('Not yet implemented');
|
throw new Error('Not yet implemented');
|
||||||
/*
|
/*
|
||||||
const data = handleSegmentationStorage(
|
const data = handleSegmentationStorage(
|
||||||
stack.imageIds,
|
stack.imageIds,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
imageDataObject = data.referenceDataObject;
|
imageDataObject = data.referenceDataObject;
|
||||||
@@ -182,7 +170,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
default:
|
default:
|
||||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||||
|
|
||||||
return imageDataObject;
|
return imageDataObject;
|
||||||
}
|
}
|
||||||
@@ -194,25 +182,31 @@ class OHIFVTKViewport extends Component {
|
|||||||
* @param {object} imageDataObject
|
* @param {object} imageDataObject
|
||||||
* @param {object} imageDataObject.vtkImageData
|
* @param {object} imageDataObject.vtkImageData
|
||||||
* @param {object} imageDataObject.imageMetaData0
|
* @param {object} imageDataObject.imageMetaData0
|
||||||
* @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth
|
* @param {number} [imageDataObject.imageMetaData0.WindowWidth] - The volume's initial WindowWidth
|
||||||
* @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter
|
* @param {number} [imageDataObject.imageMetaData0.WindowCenter] - The volume's initial WindowCenter
|
||||||
* @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc
|
* @param {string} imageDataObject.imageMetaData0.Modality - CT, MR, PT, etc
|
||||||
* @param {string} displaySetInstanceUid
|
* @param {string} displaySetInstanceUID
|
||||||
* @returns vtkVolumeActor
|
* @returns vtkVolumeActor
|
||||||
* @memberof OHIFVTKViewport
|
* @memberof OHIFVTKViewport
|
||||||
*/
|
*/
|
||||||
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
getOrCreateVolume(imageDataObject, displaySetInstanceUID) {
|
||||||
if (volumeCache[displaySetInstanceUid]) {
|
if (volumeCache[displaySetInstanceUID]) {
|
||||||
return volumeCache[displaySetInstanceUid];
|
return volumeCache[displaySetInstanceUID];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
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(
|
const { lower, upper } = _getRangeFromWindowLevels(
|
||||||
windowWidth,
|
WindowWidth,
|
||||||
windowCenter,
|
WindowCenter,
|
||||||
modality
|
Modality
|
||||||
);
|
);
|
||||||
const volumeActor = vtkVolume.newInstance();
|
const volumeActor = vtkVolume.newInstance();
|
||||||
const volumeMapper = vtkVolumeMapper.newInstance();
|
const volumeMapper = vtkVolumeMapper.newInstance();
|
||||||
@@ -236,7 +230,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
// TODO: maybe we should auto adjust samples to 1000.
|
// TODO: maybe we should auto adjust samples to 1000.
|
||||||
volumeMapper.setMaximumSamplesPerRay(4000);
|
volumeMapper.setMaximumSamplesPerRay(4000);
|
||||||
|
|
||||||
volumeCache[displaySetInstanceUid] = volumeActor;
|
volumeCache[displaySetInstanceUID] = volumeActor;
|
||||||
|
|
||||||
return volumeActor;
|
return volumeActor;
|
||||||
}
|
}
|
||||||
@@ -244,26 +238,26 @@ class OHIFVTKViewport extends Component {
|
|||||||
setStateFromProps() {
|
setStateFromProps() {
|
||||||
const { studies, displaySet } = this.props.viewportData;
|
const { studies, displaySet } = this.props.viewportData;
|
||||||
const {
|
const {
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
} = displaySet;
|
} = displaySet;
|
||||||
|
|
||||||
if (sopClassUids.length > 1) {
|
if (sopClassUIDs.length > 1) {
|
||||||
console.warn(
|
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(
|
const imageDataObject = this.getViewportData(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
sopClassUid,
|
SOPClassUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -277,7 +271,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
const volumeActor = this.getOrCreateVolume(
|
const volumeActor = this.getOrCreateVolume(
|
||||||
imageDataObject,
|
imageDataObject,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -317,9 +311,9 @@ class OHIFVTKViewport extends Component {
|
|||||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
displaySet.displaySetInstanceUid !==
|
displaySet.displaySetInstanceUID !==
|
||||||
prevDisplaySet.displaySetInstanceUid ||
|
prevDisplaySet.displaySetInstanceUID ||
|
||||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
@@ -331,7 +325,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
||||||
|
|
||||||
const numberOfFrames = insertPixelDataPromises.length;
|
const NumberOfFrames = insertPixelDataPromises.length;
|
||||||
|
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
this.setState({ isLoaded: true });
|
this.setState({ isLoaded: true });
|
||||||
@@ -341,7 +335,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
insertPixelDataPromises.forEach(promise => {
|
insertPixelDataPromises.forEach(promise => {
|
||||||
promise.then(numberProcessed => {
|
promise.then(numberProcessed => {
|
||||||
const percentComplete = Math.floor(
|
const percentComplete = Math.floor(
|
||||||
(numberProcessed * 100) / numberOfFrames
|
(numberProcessed * 100) / NumberOfFrames
|
||||||
);
|
);
|
||||||
|
|
||||||
if (percentComplete !== this.state.percentComplete) {
|
if (percentComplete !== this.state.percentComplete) {
|
||||||
@@ -373,8 +367,6 @@ class OHIFVTKViewport extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
@@ -409,21 +401,21 @@ class OHIFVTKViewport extends Component {
|
|||||||
* @private
|
* @private
|
||||||
* @param {number} [width] - the width of our window
|
* @param {number} [width] - the width of our window
|
||||||
* @param {number} [center] - the center 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
|
* @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);
|
const levelsAreNotNumbers = isNaN(center) || isNaN(width);
|
||||||
|
|
||||||
if (levelsAreNotNumbers) {
|
if (levelsAreNotNumbers) {
|
||||||
return { lower: 0, upper: 512 };
|
return { lower: 0, upper: 512 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// For PET just set the range to 0-5 SUV
|
|
||||||
if (modality === 'PT') {
|
|
||||||
return { lower: 0, upper: 5 };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lower: center - width / 2.0,
|
lower: center - width / 2.0,
|
||||||
upper: center + width / 2.0,
|
upper: center + width / 2.0,
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const VTKViewport = props => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScrollEvent = evt => {
|
const handleScrollEvent = evt => {
|
||||||
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
||||||
const viewportUid = vtkViewportApiReference.uid;
|
const viewportUID = vtkViewportApiReference.uid;
|
||||||
const viewportWasScrolled = viewportUid === evt.detail.uid;
|
const viewportWasScrolled = viewportUID === evt.detail.uid;
|
||||||
|
|
||||||
if (viewportWasScrolled) {
|
if (viewportWasScrolled) {
|
||||||
setViewportActiveHandler();
|
setViewportActiveHandler();
|
||||||
|
|||||||
@@ -70,12 +70,9 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
if (cornerstoneElement) {
|
if (cornerstoneElement) {
|
||||||
const imageId = cornerstoneElement.image.imageId;
|
const imageId = cornerstoneElement.image.imageId;
|
||||||
|
|
||||||
const { modality } = cornerstone.metaData.get(
|
const Modality = cornerstone.metaData.get('Modality', imageId);
|
||||||
'generalSeriesModule',
|
|
||||||
imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modality !== 'PT') {
|
if (Modality !== 'PT') {
|
||||||
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
import * as dcmjs from "dcmjs";
|
import * as dcmjs from 'dcmjs';
|
||||||
|
|
||||||
import OHIF from "@ohif/core";
|
import OHIF from '@ohif/core';
|
||||||
import { api } from "dicomweb-client";
|
import { api } from 'dicomweb-client';
|
||||||
|
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
|
|
||||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const displaySet = study.displaySets.find(set => {
|
const displaySet = study.displaySets.find(set => {
|
||||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||||
});
|
});
|
||||||
|
|
||||||
return displaySet;
|
return displaySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return study.displaySets.filter(set => {
|
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(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create shortcut to displaySet
|
// Create shortcut to displaySet
|
||||||
const displaySet = getDisplaySet(
|
const displaySet = getDisplaySet(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get stack from Stack Manager
|
// Get stack from Stack Manager
|
||||||
@@ -52,19 +52,19 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
|
|||||||
function retrieveDicomData(
|
function retrieveDicomData(
|
||||||
studyInstanceUID,
|
studyInstanceUID,
|
||||||
seriesInstanceUID,
|
seriesInstanceUID,
|
||||||
sopInstanceUID,
|
sopInstanceUid,
|
||||||
wadoRoot
|
wadoRoot
|
||||||
) {
|
) {
|
||||||
const config = {
|
const config = {
|
||||||
url: wadoRoot,
|
url: wadoRoot,
|
||||||
headers: DICOMWeb.getAuthorizationHeader()
|
headers: DICOMWeb.getAuthorizationHeader(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const dicomWeb = new api.DICOMwebClient(config);
|
const dicomWeb = new api.DICOMwebClient(config);
|
||||||
const options = {
|
const options = {
|
||||||
studyInstanceUID,
|
studyInstanceUID,
|
||||||
seriesInstanceUID,
|
seriesInstanceUID,
|
||||||
sopInstanceUID
|
sopInstanceUid,
|
||||||
};
|
};
|
||||||
|
|
||||||
return dicomWeb.retrieveInstance(options);
|
return dicomWeb.retrieveInstance(options);
|
||||||
@@ -72,30 +72,26 @@ function retrieveDicomData(
|
|||||||
|
|
||||||
async function handleSegmentationStorage(
|
async function handleSegmentationStorage(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
) {
|
) {
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUid === studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
const displaySet = getDisplaySet(
|
const displaySet = getDisplaySet(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
|
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
|
||||||
// from google cloud
|
// from google cloud
|
||||||
const wadoRoot = displaySet.images[0].getData().wadoRoot;
|
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(
|
const arrayBuffer = await retrieveDicomData(
|
||||||
StudyInstanceUID,
|
displaySet.images[0].getStudyInstanceUID(),
|
||||||
SeriesInstanceUID,
|
displaySet.images[0].getSeriesInstanceUID(),
|
||||||
SOPInstanceUID,
|
displaySet.images[0].getSOPInstanceUID(),
|
||||||
wadoRoot
|
wadoRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -108,16 +104,16 @@ async function handleSegmentationStorage(
|
|||||||
|
|
||||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||||
|
|
||||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||||
const displaySets = getDisplaySetsBySeries(
|
const displaySets = getDisplaySetsBySeries(
|
||||||
studies,
|
studies,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid
|
SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (displaySets.length > 1) {
|
if (displaySets.length > 1) {
|
||||||
console.warn(
|
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());
|
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
|
||||||
|
|
||||||
if (!results) {
|
if (!results) {
|
||||||
throw new Error("Fractional segmentations are not supported");
|
throw new Error('Fractional segmentations are not supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedStack = StackManager.findOrCreateStack(
|
const cachedStack = StackManager.findOrCreateStack(
|
||||||
@@ -137,13 +133,13 @@ async function handleSegmentationStorage(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
referenceDataObject,
|
referenceDataObject,
|
||||||
labelmapDataObject
|
labelmapDataObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUID,
|
||||||
stack
|
stack,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,21 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
|
||||||
activeViewportIndex
|
activeViewportIndex
|
||||||
];
|
];
|
||||||
|
|
||||||
const studies = studyMetadataManager.all();
|
const studies = studyMetadataManager.all();
|
||||||
|
|
||||||
const study = studies.find(
|
const study = studies.find(
|
||||||
study => study.studyInstanceUID === studyInstanceUid
|
study => study.studyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!study) {
|
if (!study) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid);
|
const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
|
||||||
|
|
||||||
if (!displaySet) {
|
if (!displaySet) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -3,6 +3,55 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.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)
|
# [2.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.9...@ohif/core@2.4.0) (2020-02-10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ import { studies } from '@ohif/core';
|
|||||||
|
|
||||||
const studiesMetadata = await studies.retrieveStudiesMetadata(
|
const studiesMetadata = await studies.retrieveStudiesMetadata(
|
||||||
server, // Object
|
server, // Object
|
||||||
studyInstanceUids, // Array
|
studyInstanceUIDs, // Array
|
||||||
seriesInstanceUids // Array (optional)
|
seriesInstanceUIDs // Array (optional)
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "2.4.0",
|
"version": "2.5.3",
|
||||||
"description": "Generic business logic for web-based medical imaging applications",
|
"description": "Generic business logic for web-based medical imaging applications",
|
||||||
"author": "OHIF Core Team",
|
"author": "OHIF Core Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "^4.12.0",
|
"cornerstone-tools": "4.12.0",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dicom-parser": "^1.8.3"
|
"dicom-parser": "^1.8.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const retrieveMeasurements = server => {
|
|||||||
|
|
||||||
const serverUrl = server.wadoRoot;
|
const serverUrl = server.wadoRoot;
|
||||||
const studies = utils.studyMetadataManager.all();
|
const studies = utils.studyMetadataManager.all();
|
||||||
|
|
||||||
const latestSeries = findMostRecentStructuredReport(studies);
|
const latestSeries = findMostRecentStructuredReport(studies);
|
||||||
|
|
||||||
if (!latestSeries) return Promise.resolve({});
|
if (!latestSeries) return Promise.resolve({});
|
||||||
@@ -57,17 +58,17 @@ const storeMeasurements = async (measurementData, filter, server) => {
|
|||||||
const serverUrl = server.wadoRoot;
|
const serverUrl = server.wadoRoot;
|
||||||
const firstMeasurementKey = Object.keys(measurementData)[0];
|
const firstMeasurementKey = Object.keys(measurementData)[0];
|
||||||
const firstMeasurement = measurementData[firstMeasurementKey][0];
|
const firstMeasurement = measurementData[firstMeasurementKey][0];
|
||||||
const studyInstanceUid =
|
const StudyInstanceUID =
|
||||||
firstMeasurement && firstMeasurement.studyInstanceUid;
|
firstMeasurement && firstMeasurement.StudyInstanceUID;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stowSRFromMeasurements(measurementData, serverUrl);
|
await stowSRFromMeasurements(measurementData, serverUrl);
|
||||||
if (studyInstanceUid) {
|
if (StudyInstanceUID) {
|
||||||
studies.deleteStudyMetadataPromise(studyInstanceUid);
|
studies.deleteStudyMetadataPromise(StudyInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Measurements were saved with success',
|
message: 'Measurements saved successfully',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as dcmjs from 'dcmjs';
|
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
|
* 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) => {
|
const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||||
// Get the dicom data as an Object
|
// Get the dicom data as an Object
|
||||||
|
|
||||||
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
||||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
dicomData.dict
|
dicomData.dict
|
||||||
@@ -30,17 +31,18 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
|||||||
measurementData[toolName] = [];
|
measurementData[toolName] = [];
|
||||||
|
|
||||||
measurements.forEach(measurement => {
|
measurements.forEach(measurement => {
|
||||||
const instanceMetadata = findInstanceMetadataBySopInstanceUid(
|
const instanceMetadata = findInstanceMetadataBySopInstanceUID(
|
||||||
displaySets,
|
displaySets,
|
||||||
measurement.sopInstanceUid
|
measurement.sopInstanceUid
|
||||||
);
|
);
|
||||||
|
|
||||||
const { _study: study, _series: series } = instanceMetadata;
|
const { _study: study, _series: series } = instanceMetadata;
|
||||||
const { studyInstanceUid, patientId } = study;
|
const { StudyInstanceUID, PatientID } = study;
|
||||||
const { seriesInstanceUid } = series;
|
const { SeriesInstanceUID } = series;
|
||||||
const { sopInstanceUid, frameIndex } = measurement;
|
const { sopInstanceUid, frameIndex } = measurement;
|
||||||
const imagePath = getImagePath(
|
const imagePath = getImagePath(
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
sopInstanceUid,
|
sopInstanceUid,
|
||||||
frameIndex
|
frameIndex
|
||||||
);
|
);
|
||||||
@@ -56,9 +58,10 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
|||||||
const toolData = Object.assign({}, measurement, {
|
const toolData = Object.assign({}, measurement, {
|
||||||
imageId,
|
imageId,
|
||||||
imagePath,
|
imagePath,
|
||||||
seriesInstanceUid,
|
SOPInstanceUID: sopInstanceUid,
|
||||||
studyInstanceUid,
|
SeriesInstanceUID,
|
||||||
patientId,
|
StudyInstanceUID,
|
||||||
|
PatientID,
|
||||||
measurementNumber: ++measurementNumber,
|
measurementNumber: ++measurementNumber,
|
||||||
timepointId: currentTimepointId,
|
timepointId: currentTimepointId,
|
||||||
toolType: toolName,
|
toolType: toolName,
|
||||||
@@ -75,19 +78,19 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
|||||||
/**
|
/**
|
||||||
* Function to create imagePath with all imageData related
|
* Function to create imagePath with all imageData related
|
||||||
*
|
*
|
||||||
* @param {string} studyInstanceUid
|
* @param {string} StudyInstanceUID
|
||||||
* @param {string} seriesInstanceUid
|
* @param {string} SeriesInstanceUID
|
||||||
* @param {string} sopInstanceUid
|
* @param {string} SOPInstanceUID
|
||||||
* @param {string} frameIndex
|
* @param {string} frameIndex
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getImagePath = (
|
const getImagePath = (
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex
|
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
|
* Should Find the requested instance metadata into the displaySets and return
|
||||||
*
|
*
|
||||||
* @param {Array} displaySets - List of displaySets
|
* @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
|
* @returns {Object} - instance metadata found
|
||||||
*/
|
*/
|
||||||
const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => {
|
||||||
let instanceFound;
|
let instanceFound;
|
||||||
|
|
||||||
displaySets.find(displaySet => {
|
displaySets.find(displaySet => {
|
||||||
if (!displaySet.images) return false;
|
if (!displaySet.images) return false;
|
||||||
|
|
||||||
instanceFound = displaySet.images.find(
|
instanceFound = displaySet.images.find(
|
||||||
instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid
|
instanceMetadata =>
|
||||||
|
instanceMetadata.getSOPInstanceUID() === SOPInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return !!instanceFound;
|
return !!instanceFound;
|
||||||
@@ -21,4 +22,4 @@ const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
|||||||
return instanceFound;
|
return instanceFound;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default findInstanceMetadataBySopInstanceUid;
|
export default findInstanceMetadataBySopInstanceUID;
|
||||||
@@ -37,9 +37,9 @@ const isStructuredReportSeries = series => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const firstInstance = series.getFirstInstance();
|
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) => {
|
const compareSeriesDate = (series1, series2) => {
|
||||||
return (
|
return (
|
||||||
series1._data.seriesDate > series2._data.seriesDate ||
|
series1._data.SeriesDate > series2._data.SeriesDate ||
|
||||||
(series1._data.seriesDate === series2._data.seriesDate &&
|
(series1._data.SeriesDate === series2._data.SeriesDate &&
|
||||||
series1._data.seriesTime > series2._data.seriesTime)
|
series1._data.SeriesTime > series2._data.SeriesTime)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
export default function getModalities(modality, modalitiesInStudy) {
|
export default function getModalities(Modality, ModalitiesInStudy) {
|
||||||
if (!modality && !modalitiesInStudy) {
|
if (!Modality && !ModalitiesInStudy) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalities = modality || {
|
const modalities = Modality || {
|
||||||
vr: 'CS',
|
vr: 'CS',
|
||||||
Value: [],
|
Value: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (modalitiesInStudy) {
|
if (ModalitiesInStudy) {
|
||||||
if (modalities.vr && modalities.vr === modalitiesInStudy.vr) {
|
if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {
|
||||||
for (let i = 0; i < modalitiesInStudy.Value.length; i++) {
|
for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {
|
||||||
const value = modalitiesInStudy.Value[i];
|
const value = ModalitiesInStudy.Value[i];
|
||||||
if (modalities.Value.indexOf(value) === -1) {
|
if (modalities.Value.indexOf(value) === -1) {
|
||||||
modalities.Value.push(value);
|
modalities.Value.push(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return modalitiesInStudy;
|
return ModalitiesInStudy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
import getModalities from './getModalities';
|
import getModalities from './getModalities';
|
||||||
|
|
||||||
describe('getModalities', () => {
|
describe('getModalities', () => {
|
||||||
test('should return an empty object when modality and modalitiesInStudy are not present', () => {
|
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||||
const modality = null;
|
const Modality = null;
|
||||||
const modalitiesInStudy = 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', () => {
|
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||||
const modality = null;
|
const Modality = null;
|
||||||
const modalitiesInStudy = 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', () => {
|
test('should return modalities in Study when Modality is not defined', () => {
|
||||||
const modality = null;
|
const Modality = null;
|
||||||
|
|
||||||
const modalitiesInStudy = {
|
const ModalitiesInStudy = {
|
||||||
Value: ['MOCKED_VALUE'],
|
Value: ['MOCKED_VALUE'],
|
||||||
vr: 'MOCKED_VALUE',
|
vr: 'MOCKED_VALUE',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||||
modalitiesInStudy
|
ModalitiesInStudy
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return only the modalitues that exists in modalitiesInStudy', () => {
|
test('should return only the modalitues that exists in ModalitiesInStudy', () => {
|
||||||
const modality = {
|
const Modality = {
|
||||||
Value: ['DESIRED_VALUE'],
|
Value: ['DESIRED_VALUE'],
|
||||||
vr: 'DESIRED_VR',
|
vr: 'DESIRED_VR',
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalitiesInStudy = {
|
const ModalitiesInStudy = {
|
||||||
Value: ['DESIRED_VALUE', 'NOT_DESIRED_VALUE'],
|
Value: ['DESIRED_VALUE', 'NOT_DESIRED_VALUE'],
|
||||||
vr: 'DESIRED_VR',
|
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', () => {
|
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy', () => {
|
||||||
const modality = {
|
const Modality = {
|
||||||
Value: ['DESIRED_VALUE'],
|
Value: ['DESIRED_VALUE'],
|
||||||
vr: 'DESIRED_VR',
|
vr: 'DESIRED_VR',
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalitiesInStudy = {
|
const ModalitiesInStudy = {
|
||||||
Value: ['NOT_DESIRED_VALUE'],
|
Value: ['NOT_DESIRED_VALUE'],
|
||||||
vr: 'DESIRED_VR',
|
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', () => {
|
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy VR', () => {
|
||||||
const modality = {
|
const Modality = {
|
||||||
Value: ['DESIRED_VALUE'],
|
Value: ['DESIRED_VALUE'],
|
||||||
vr: 'DESIRED_VR',
|
vr: 'DESIRED_VR',
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalitiesInStudy = {
|
const ModalitiesInStudy = {
|
||||||
Value: ['NOT_DESIRED_VALUE'],
|
Value: ['NOT_DESIRED_VALUE'],
|
||||||
vr: 'ANOTHER_VR',
|
vr: 'ANOTHER_VR',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||||
modalitiesInStudy
|
ModalitiesInStudy
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
warn: jest.fn(),
|
warn: jest.fn(),
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import hotkeys from './hotkeys';
|
import hotkeys from './../utils/hotkeys';
|
||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +11,7 @@ import log from './../log.js';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class HotkeysManager {
|
export class HotkeysManager {
|
||||||
constructor(commandsManager) {
|
constructor(commandsManager, servicesManager) {
|
||||||
this.hotkeyDefinitions = {};
|
this.hotkeyDefinitions = {};
|
||||||
this.hotkeyDefaults = [];
|
this.hotkeyDefaults = [];
|
||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
@@ -22,9 +22,19 @@ export class HotkeysManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._servicesManager = servicesManager;
|
||||||
this._commandsManager = commandsManager;
|
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
|
* Disables all hotkeys. Hotkeys added while disabled will not listen for
|
||||||
* input.
|
* input.
|
||||||
@@ -45,38 +55,57 @@ export class HotkeysManager {
|
|||||||
/**
|
/**
|
||||||
* Registers a list of hotkeydefinitions.
|
* Registers a list of hotkeydefinitions.
|
||||||
*
|
*
|
||||||
* @param {HotkeyDefinition[] | Object} hotkeyDefinitions Contains hotkeys definitions
|
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||||
*/
|
*/
|
||||||
setHotkeys(hotkeyDefinitions) {
|
setHotkeys(hotkeyDefinitions = []) {
|
||||||
const definitions = Array.isArray(hotkeyDefinitions)
|
try {
|
||||||
? [...hotkeyDefinitions]
|
const definitions = this._getValidDefinitions(hotkeyDefinitions);
|
||||||
: this._parseToArrayLike(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
|
* Set default hotkey bindings. These
|
||||||
* values are used in `this.restoreDefaultBindings`.
|
* 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)
|
const definitions = Array.isArray(hotkeyDefinitions)
|
||||||
? [...hotkeyDefinitions]
|
? [...hotkeyDefinitions]
|
||||||
: this._parseToArrayLike(hotkeyDefinitions);
|
: this._parseToArrayLike(hotkeyDefinitions);
|
||||||
|
|
||||||
this.hotkeyDefaults = definitions;
|
return definitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It parses given object containing hotkeyDefinition to array like.
|
* 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
|
* 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[]}
|
* @returns {HotkeyDefinition[]}
|
||||||
*/
|
*/
|
||||||
_parseToArrayLike(hotkeyDefinitionsObj) {
|
_parseToArrayLike(hotkeyDefinitionsObj = {}) {
|
||||||
const copy = { ...hotkeyDefinitionsObj };
|
const copy = { ...hotkeyDefinitionsObj };
|
||||||
return Object.entries(copy).map(entryValue =>
|
return Object.entries(copy).map(entryValue =>
|
||||||
this._parseToHotKeyObj(entryValue[0], entryValue[1])
|
this._parseToHotKeyObj(entryValue[0], entryValue[1])
|
||||||
@@ -127,11 +156,13 @@ export class HotkeysManager {
|
|||||||
if (previouslyRegisteredDefinition) {
|
if (previouslyRegisteredDefinition) {
|
||||||
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
||||||
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
||||||
|
log.info(`Unbinding ${commandName} from ${previouslyRegisteredKeys}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set definition & bind
|
// Set definition & bind
|
||||||
this.hotkeyDefinitions[commandName] = { keys, label };
|
this.hotkeyDefinitions[commandName] = { keys, label };
|
||||||
this._bindHotkeys(commandName, keys);
|
this._bindHotkeys(commandName, keys);
|
||||||
|
log.info(`Binding ${commandName} to ${keys}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,12 +198,11 @@ export class HotkeysManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isKeyArray = keys instanceof Array;
|
const isKeyArray = keys instanceof Array;
|
||||||
if (isKeyArray) {
|
const combinedKeys = isKeyArray ? keys.join('+') : keys;
|
||||||
keys.forEach(key => this._bindHotkeys(commandName, key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotkeys.bind(keys, evt => {
|
hotkeys.bind(combinedKeys, evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
this._commandsManager.runCommand(commandName, { evt });
|
this._commandsManager.runCommand(commandName, { evt });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,7 +223,8 @@ export class HotkeysManager {
|
|||||||
|
|
||||||
const isKeyArray = keys instanceof Array;
|
const isKeyArray = keys instanceof Array;
|
||||||
if (isKeyArray) {
|
if (isKeyArray) {
|
||||||
keys.forEach(key => this._unbindHotkeys(commandName, key));
|
const combinedKeys = keys.join('+');
|
||||||
|
this._unbindHotkeys(commandName, combinedKeys);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import CommandsManager from './CommandsManager.js';
|
import CommandsManager from './CommandsManager.js';
|
||||||
import HotkeysManager from './HotkeysManager.js';
|
import HotkeysManager from './HotkeysManager.js';
|
||||||
import hotkeys from './hotkeys';
|
import hotkeys from './../utils/hotkeys';
|
||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
jest.mock('./CommandsManager.js');
|
jest.mock('./CommandsManager.js');
|
||||||
jest.mock('./hotkeys');
|
jest.mock('./../utils/hotkeys');
|
||||||
jest.mock('./../log.js');
|
jest.mock('./../log.js');
|
||||||
|
|
||||||
describe('HotkeysManager', () => {
|
describe('HotkeysManager', () => {
|
||||||
@@ -60,7 +60,10 @@ describe('HotkeysManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('enable()', () => {
|
describe('enable()', () => {
|
||||||
beforeEach(() => hotkeys.unpause.mockClear());
|
beforeEach(() => {
|
||||||
|
hotkeys.unpause = jest.fn();
|
||||||
|
hotkeys.unpause.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
it('sets isEnabled property to true', () => {
|
it('sets isEnabled property to true', () => {
|
||||||
hotkeysManager.disable();
|
hotkeysManager.disable();
|
||||||
@@ -139,20 +142,18 @@ describe('HotkeysManager', () => {
|
|||||||
expectedHotkeyDefinition
|
expectedHotkeyDefinition
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('calls hotkeys.bind for all keys in array', () => {
|
it('calls hotkeys.bind for the group of keys', () => {
|
||||||
const definition = { commandName: 'dance', keys: ['h', 'e', 'l', 'o'] };
|
const definition = { commandName: 'dance', keys: ['shift', 'e'] };
|
||||||
|
|
||||||
hotkeysManager.registerHotkeys(definition);
|
hotkeysManager.registerHotkeys(definition);
|
||||||
|
|
||||||
expect(hotkeys.bind.mock.calls.length).toBe(definition.keys.length);
|
expect(hotkeys.bind.mock.calls.length).toBe(1);
|
||||||
definition.keys.forEach((key, i) =>
|
expect(hotkeys.bind.mock.calls[0][0]).toBe('shift+e');
|
||||||
expect(hotkeys.bind.mock.calls[i][0]).toBe(key)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('calls hotkeys.unbind if commandName was previously registered, for each previously registered set of keys', () => {
|
it('calls hotkeys.unbind if commandName was previously registered, for each previously registered set of keys', () => {
|
||||||
const firstDefinition = {
|
const firstDefinition = {
|
||||||
commandName: 'dance',
|
commandName: 'dance',
|
||||||
keys: ['h', 'e', 'l', 'o'],
|
keys: ['alt', 'e'],
|
||||||
};
|
};
|
||||||
const secondDefinition = { commandName: 'dance', keys: 'a' };
|
const secondDefinition = { commandName: 'dance', keys: 'a' };
|
||||||
|
|
||||||
@@ -161,12 +162,8 @@ describe('HotkeysManager', () => {
|
|||||||
// Second call
|
// Second call
|
||||||
hotkeysManager.registerHotkeys(secondDefinition);
|
hotkeysManager.registerHotkeys(secondDefinition);
|
||||||
|
|
||||||
expect(hotkeys.unbind.mock.calls.length).toBe(
|
expect(hotkeys.unbind.mock.calls.length).toBe(1);
|
||||||
firstDefinition.keys.length
|
expect(hotkeys.unbind.mock.calls[0][0]).toBe('alt+e');
|
||||||
);
|
|
||||||
firstDefinition.keys.forEach((key, i) =>
|
|
||||||
expect(hotkeys.unbind.mock.calls[i][0]).toBe(key)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,412 +1,508 @@
|
|||||||
import { parsingUtils } from '../lib/parsingUtils';
|
import * as dcmjs from 'dcmjs';
|
||||||
|
import queryString from 'query-string';
|
||||||
const FUNCTION = 'function';
|
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 {
|
class MetadataProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
// Define the main "metadataLookup" private property as an immutable property.
|
// Define the main "metadataLookup" private property as an immutable property.
|
||||||
Object.defineProperty(this, 'metadataLookup', {
|
Object.defineProperty(this, 'studies', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: new Map(),
|
value: new Map(),
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(this, 'imageIdToUIDs', {
|
||||||
// Local reference to provider function bound to current instance.
|
|
||||||
Object.defineProperty(this, '_provider', {
|
|
||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: true,
|
writable: false,
|
||||||
value: null,
|
value: new Map(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
|
||||||
* Cornerstone Metadata provider to store image meta data
|
let dicomJSONDataset;
|
||||||
* 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 = {};
|
|
||||||
|
|
||||||
metadata.frameNumber = data.frameNumber;
|
// If Arraybuffer, parse to DICOMJSON before naturalizing.
|
||||||
|
if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {
|
||||||
|
const dicomData = DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer);
|
||||||
|
|
||||||
metadata.study = {
|
dicomJSONDataset = dicomData.dict;
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
value = image.instance[attrName];
|
dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value === null ? defaultValue : value;
|
// Check if dataset is already naturalized.
|
||||||
}
|
|
||||||
|
|
||||||
getFromDataSet(dataSet, type, tag) {
|
let naturalizedDataset;
|
||||||
if (!dataSet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn = dataSet[type];
|
if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {
|
||||||
if (!fn) {
|
naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
return;
|
dicomJSONDataset
|
||||||
}
|
|
||||||
|
|
||||||
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'
|
|
||||||
);
|
);
|
||||||
frameInstancePointer = frameInstancePointerNames[frameInstancePointerTag];
|
|
||||||
} else {
|
} else {
|
||||||
frameInstancePointer = image.instance.frameIncrementPointer;
|
naturalizedDataset = dicomJSONDataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
return frameInstancePointer || '';
|
const {
|
||||||
}
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
} = naturalizedDataset;
|
||||||
|
|
||||||
getFrameTimeVector(image) {
|
const study = this._getAndCacheStudy(StudyInstanceUID);
|
||||||
const dataSet = image.data;
|
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
|
||||||
|
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
|
||||||
|
|
||||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
Object.assign(instance, naturalizedDataset);
|
||||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
|
||||||
return parsingUtils.floatArray(dataSet, 'x00181065');
|
if (options.server) {
|
||||||
|
await this._checkBulkDataAndInlineBinaries(instance, options.server);
|
||||||
}
|
}
|
||||||
|
|
||||||
return image.instance.frameTimeVector;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrameTime(image) {
|
addImageIdToUIDs(imageId, uids) {
|
||||||
const dataSet = image.data;
|
// 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)) {
|
this.imageIdToUIDs.set(imageId, uids);
|
||||||
// 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.
|
_getAndCacheStudy(StudyInstanceUID) {
|
||||||
return dataSet.floatString('x00181063', -1);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
||||||
* Updates the related metadata for missing fields given a specified image
|
let instance = series.instances.get(SOPInstanceUID);
|
||||||
*
|
|
||||||
* @param image
|
if (!instance) {
|
||||||
*/
|
instance = {};
|
||||||
updateMetadata(image) {
|
series.instances.set(SOPInstanceUID, instance);
|
||||||
const imageMetadata = this.metadataLookup.get(image.imageId);
|
}
|
||||||
if (!imageMetadata) {
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageMetadata.patient.age =
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = uids;
|
||||||
imageMetadata.patient.age ||
|
|
||||||
this.getFromDataSet(image.data, 'string', 'x00101010');
|
|
||||||
|
|
||||||
imageMetadata.instance.rows = imageMetadata.instance.rows || image.rows;
|
return this._getInstanceData(
|
||||||
imageMetadata.instance.columns =
|
StudyInstanceUID,
|
||||||
imageMetadata.instance.columns || image.columns;
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID
|
||||||
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
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (numberOfFrames > 0) {
|
get(query, imageId, options = { fallback: false }) {
|
||||||
// set multi-frame image indicator
|
const instance = this._getInstance(imageId);
|
||||||
imageInfo.isMultiframeImage = true;
|
|
||||||
imageInfo.numberOfFrames = numberOfFrames;
|
|
||||||
|
|
||||||
// (0028,0009) = Frame Increment Pointer
|
if (query === INSTANCE) {
|
||||||
const frameIncrementPointer = this.getFrameIncrementPointer(image);
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
if (frameIncrementPointer === 'frameTimeVector') {
|
return this.getTagFromInstance(query, instance, options);
|
||||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
}
|
||||||
const frameTimeVector = this.getFrameTimeVector(image);
|
|
||||||
|
|
||||||
if (frameTimeVector instanceof Array && frameTimeVector.length > 0) {
|
getTag(query, imageId, options) {
|
||||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
return this.get(query, imageId, options);
|
||||||
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);
|
|
||||||
|
|
||||||
if (frameTime > 0) {
|
getInstance(imageId) {
|
||||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
return this.get(INSTANCE, imageId);
|
||||||
imageInfo.frameTime = frameTime;
|
}
|
||||||
imageInfo.averageFrameRate = 1000 / frameTime;
|
|
||||||
}
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getCornerstoneWADOImageLoaderTag(wadoImageLoaderTag, instance) {
|
||||||
* Get a bound reference to the provider function.
|
let metadata;
|
||||||
*/
|
|
||||||
getProvider() {
|
switch (wadoImageLoaderTag) {
|
||||||
let provider = this._provider;
|
case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE:
|
||||||
if (typeof this._provider !== FUNCTION) {
|
const { SeriesDate, SeriesTime } = instance;
|
||||||
provider = this.provider.bind(this);
|
|
||||||
this._provider = provider;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getInstanceData(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) {
|
||||||
* Looks up metadata for Cornerstone Tools given a specified type and imageId
|
const study = this.studies.get(StudyInstanceUID);
|
||||||
* A type may be, e.g. 'study', or 'patient', or 'imagePlaneModule'. These types
|
|
||||||
* are keys in the stored metadata objects.
|
if (!study) {
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
* @param imageId
|
|
||||||
* @returns {Object} Relevant metadata of the specified type
|
|
||||||
*/
|
|
||||||
provider(type, imageId) {
|
|
||||||
const imageMetadata = this.metadataLookup.get(imageId);
|
|
||||||
if (!imageMetadata) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageMetadata.hasOwnProperty(type)) {
|
const series = study.series.get(SeriesInstanceUID);
|
||||||
return imageMetadata[type];
|
|
||||||
|
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 OHIFError from './OHIFError';
|
||||||
import { StudyMetadata } from './metadata/StudyMetadata';
|
import { StudyMetadata } from './metadata/StudyMetadata';
|
||||||
import { StudyMetadataSource } from './StudyMetadataSource.js';
|
import { StudyMetadataSource } from './StudyMetadataSource.js';
|
||||||
import { StudySummary } from './metadata/StudySummary';
|
|
||||||
import { retrieveStudyMetadata } from '../studies/retrieveStudyMetadata.js';
|
import { retrieveStudyMetadata } from '../studies/retrieveStudyMetadata.js';
|
||||||
|
|
||||||
export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
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.
|
* 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) {
|
loadStudy(study) {
|
||||||
if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) {
|
if (!(study instanceof StudyMetadata)) {
|
||||||
throw new OHIFError(
|
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) {
|
if (study instanceof StudyMetadata) {
|
||||||
const alreadyLoaded = OHIF.viewer.Studies.findBy({
|
const alreadyLoaded = OHIF.viewer.Studies.findBy({
|
||||||
studyInstanceUid: studyInstanceUID,
|
StudyInstanceUID: studyInstanceUID,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!alreadyLoaded) {
|
if (!alreadyLoaded) {
|
||||||
@@ -49,7 +48,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
|||||||
// Create study metadata object
|
// Create study metadata object
|
||||||
const studyMetadata = new StudyMetadata(
|
const studyMetadata = new StudyMetadata(
|
||||||
studyInfo,
|
studyInfo,
|
||||||
studyInfo.studyInstanceUid
|
studyInfo.StudyInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get Study display sets
|
// Get Study display sets
|
||||||
@@ -72,10 +71,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
|||||||
// Set some studyInfo properties
|
// Set some studyInfo properties
|
||||||
studyInfo.selected = true;
|
studyInfo.selected = true;
|
||||||
studyInfo.displaySets = studyMetadata.getDisplaySets();
|
studyInfo.displaySets = studyMetadata.getDisplaySets();
|
||||||
|
|
||||||
// Updates WADO-RS metaDataManager
|
|
||||||
updateMetaDataManager(studyInfo);
|
|
||||||
|
|
||||||
studyMetadataManager.add(studyMetadata);
|
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() {
|
_getProgressId() {
|
||||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||||
return 'StackProgress:' + displaySetInstanceUid;
|
return 'StackProgress:' + displaySetInstanceUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearProgress() {
|
_clearProgress() {
|
||||||
@@ -362,7 +362,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
|
|
||||||
_logProgress() {
|
_logProgress() {
|
||||||
const totalFramesCount = this.stack.imageIds.length;
|
const totalFramesCount = this.stack.imageIds.length;
|
||||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||||
let progressBar = '[';
|
let progressBar = '[';
|
||||||
|
|
||||||
for (let i = 0; i < totalFramesCount; i++) {
|
for (let i = 0; i < totalFramesCount; i++) {
|
||||||
@@ -371,7 +371,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
progressBar += ']';
|
progressBar += ']';
|
||||||
log.info(`${displaySetInstanceUid}: ${progressBar}`);
|
log.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,12 +388,12 @@ class StudyLoadingListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
const displaySetInstanceUID = stack.displaySetInstanceUID;
|
||||||
|
|
||||||
if (!this.listeners[displaySetInstanceUid]) {
|
if (!this.listeners[displaySetInstanceUID]) {
|
||||||
const listener = this._createListener(stack, stackMetaData);
|
const listener = this._createListener(stack, stackMetaData);
|
||||||
if (listener) {
|
if (listener) {
|
||||||
this.listeners[displaySetInstanceUid] = listener;
|
this.listeners[displaySetInstanceUID] = listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,12 +424,12 @@ class StudyLoadingListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
const displaySetInstanceUids = Object.keys(this.listeners);
|
const displaySetInstanceUIDs = Object.keys(this.listeners);
|
||||||
const length = displaySetInstanceUids.length;
|
const length = displaySetInstanceUIDs.length;
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const displaySetInstanceUid = displaySetInstanceUids[i];
|
const displaySetInstanceUID = displaySetInstanceUIDs[i];
|
||||||
const displaySet = this.listeners[displaySetInstanceUid];
|
const displaySet = this.listeners[displaySetInstanceUID];
|
||||||
|
|
||||||
displaySet.destroy();
|
displaySet.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class StudyMetadataSource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load study info and study metadata for a given study into the viewer.
|
* 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) {
|
loadStudy(study) {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -94,17 +94,23 @@ export class StudyPrefetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStudy(image) {
|
getStudy(image) {
|
||||||
const studyMetadata = cornerstone.metaData.get('study', image.imageId);
|
const StudyInstanceUID = cornerstone.metaData.get(
|
||||||
|
'StudyInstanceUID',
|
||||||
|
image.imageId
|
||||||
|
);
|
||||||
return OHIF.viewer.Studies.find(
|
return OHIF.viewer.Studies.find(
|
||||||
study => study.studyInstanceUid === studyMetadata.studyInstanceUid
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeries(study, image) {
|
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);
|
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
|
||||||
|
|
||||||
return studyMetadata.getSeriesByUID(seriesMetadata.seriesInstanceUid);
|
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstance(series, image) {
|
getInstance(series, image) {
|
||||||
@@ -112,13 +118,13 @@ export class StudyPrefetcher {
|
|||||||
'instance',
|
'instance',
|
||||||
image.imageId
|
image.imageId
|
||||||
);
|
);
|
||||||
return series.getInstanceByUID(instanceMetadata.sopInstanceUid);
|
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveDisplaySet(displaySets, instance) {
|
getActiveDisplaySet(displaySets, instance) {
|
||||||
return displaySets.find(displaySet => {
|
return displaySets.find(displaySet => {
|
||||||
return displaySet.images.some(displaySetImage => {
|
return displaySet.images.some(displaySetImage => {
|
||||||
return displaySetImage.sopInstanceUid === instance.sopInstanceUid;
|
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
import { InstanceMetadata, SeriesMetadata, StudyMetadata } from './metadata';
|
||||||
InstanceMetadata,
|
|
||||||
SeriesMetadata,
|
|
||||||
StudyMetadata,
|
|
||||||
StudySummary,
|
|
||||||
} from './metadata';
|
|
||||||
|
|
||||||
import CommandsManager from './CommandsManager.js';
|
import CommandsManager from './CommandsManager.js';
|
||||||
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
||||||
import HotkeysManager from './HotkeysManager.js';
|
import HotkeysManager from './HotkeysManager.js';
|
||||||
import ImageSet from './ImageSet';
|
import ImageSet from './ImageSet';
|
||||||
import MetadataProvider from './MetadataProvider.js';
|
import MetadataProvider from './MetadataProvider';
|
||||||
import OHIFError from './OHIFError.js';
|
import OHIFError from './OHIFError.js';
|
||||||
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
||||||
import { StackLoadingListener } from './StudyLoadingListener';
|
import { StackLoadingListener } from './StudyLoadingListener';
|
||||||
@@ -31,7 +26,6 @@ export {
|
|||||||
StudyMetadata,
|
StudyMetadata,
|
||||||
SeriesMetadata,
|
SeriesMetadata,
|
||||||
InstanceMetadata,
|
InstanceMetadata,
|
||||||
StudySummary,
|
|
||||||
TypeSafeCollection,
|
TypeSafeCollection,
|
||||||
OHIFError,
|
OHIFError,
|
||||||
StudyMetadataSource,
|
StudyMetadataSource,
|
||||||
@@ -50,7 +44,6 @@ const classes = {
|
|||||||
StudyMetadata,
|
StudyMetadata,
|
||||||
SeriesMetadata,
|
SeriesMetadata,
|
||||||
InstanceMetadata,
|
InstanceMetadata,
|
||||||
StudySummary,
|
|
||||||
TypeSafeCollection,
|
TypeSafeCollection,
|
||||||
OHIFError,
|
OHIFError,
|
||||||
StudyMetadataSource,
|
StudyMetadataSource,
|
||||||
|
|||||||
@@ -8,20 +8,12 @@ import OHIFError from '../OHIFError.js';
|
|||||||
|
|
||||||
const UNDEFINED = 'undefined';
|
const UNDEFINED = 'undefined';
|
||||||
const STRING = 'string';
|
const STRING = 'string';
|
||||||
const STUDY_INSTANCE_UID = 'x0020000d';
|
|
||||||
const SERIES_INSTANCE_UID = 'x0020000e';
|
|
||||||
|
|
||||||
export class InstanceMetadata extends Metadata {
|
export class InstanceMetadata extends Metadata {
|
||||||
constructor(data, uid) {
|
constructor(data, uid) {
|
||||||
super(data, uid);
|
super(data, uid);
|
||||||
// Initialize Private Properties
|
// Initialize Private Properties
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
_sopInstanceUID: {
|
|
||||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
_imageId: {
|
_imageId: {
|
||||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
configurable: true, // configurable so that it can be redefined in sub-classes...
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
@@ -43,14 +35,14 @@ export class InstanceMetadata extends Metadata {
|
|||||||
*/
|
*/
|
||||||
_definePublicProperties() {
|
_definePublicProperties() {
|
||||||
/**
|
/**
|
||||||
* Property: this.sopInstanceUID
|
* Property: this.SOPInstanceUID
|
||||||
* Same as this.getSOPInstanceUID()
|
* Same as this.getSOPInstanceUID()
|
||||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||||
* sopInstanceCollection.findBy({
|
* 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,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
get: function() {
|
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.
|
* Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||||
*/
|
*/
|
||||||
getStudyInstanceUID() {
|
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.
|
* Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||||
*/
|
*/
|
||||||
getSeriesInstanceUID() {
|
getSeriesInstanceUID() {
|
||||||
return this.getTagValue(SERIES_INSTANCE_UID, null);
|
return this.getTagValue('SeriesInstanceUID', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the SOPInstanceUID of the current instance.
|
* Returns the SOPInstanceUID of the current instance.
|
||||||
*/
|
*/
|
||||||
getSOPInstanceUID() {
|
getSOPInstanceUID() {
|
||||||
return this._sopInstanceUID;
|
return this.getTagValue('SOPInstanceUID', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Improve this... (E.g.: blob data)
|
// @TODO: Improve this... (E.g.: blob data)
|
||||||
@@ -127,13 +119,6 @@ export class InstanceMetadata extends Metadata {
|
|||||||
return typeof value === STRING ? parseInt(value) : value;
|
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.
|
* 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 { InstanceMetadata } from './InstanceMetadata';
|
||||||
import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions.js';
|
|
||||||
import getImageId from '../../utils/getImageId.js';
|
import getImageId from '../../utils/getImageId.js';
|
||||||
|
|
||||||
export class OHIFInstanceMetadata extends InstanceMetadata {
|
export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||||
@@ -20,7 +19,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: instance.sopInstanceUid,
|
value: instance.SOPInstanceUID,
|
||||||
},
|
},
|
||||||
_study: {
|
_study: {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
@@ -56,16 +55,16 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
return this._cache[tagOrProperty];
|
return this._cache[tagOrProperty];
|
||||||
}
|
}
|
||||||
|
|
||||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
const instanceData = this._instance.metadata;
|
||||||
|
|
||||||
// Search property value in the whole study metadata chain...
|
// Search property value in the whole study metadata chain...
|
||||||
let rawValue;
|
let rawValue;
|
||||||
if (propertyName in this._instance) {
|
if (tagOrProperty in instanceData) {
|
||||||
rawValue = this._instance[propertyName];
|
rawValue = instanceData[tagOrProperty];
|
||||||
} else if (propertyName in this._series) {
|
} else if (tagOrProperty in this._series) {
|
||||||
rawValue = this._series[propertyName];
|
rawValue = this._series[tagOrProperty];
|
||||||
} else if (propertyName in this._study) {
|
} else if (tagOrProperty in this._study) {
|
||||||
rawValue = this._study[propertyName];
|
rawValue = this._study[tagOrProperty];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawValue !== void 0) {
|
if (rawValue !== void 0) {
|
||||||
@@ -79,12 +78,10 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
|
|
||||||
// Override
|
// Override
|
||||||
tagExists(tagOrProperty) {
|
tagExists(tagOrProperty) {
|
||||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
propertyName in this._instance ||
|
tagOrProperty in this._instance.metadata ||
|
||||||
propertyName in this._series ||
|
tagOrProperty in this._series ||
|
||||||
propertyName in this._study
|
tagOrProperty in this._study
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,28 +94,4 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
|
|
||||||
return this._imageId;
|
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,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: series.seriesInstanceUid,
|
value: series.SeriesInstanceUID,
|
||||||
});
|
});
|
||||||
|
|
||||||
// populate internal list of instances...
|
// populate internal list of instances...
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export class OHIFStudyMetadata extends StudyMetadata {
|
|||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: study.studyInstanceUid,
|
value: study.StudyInstanceUID,
|
||||||
});
|
});
|
||||||
|
|
||||||
// populate internal list of series
|
// populate internal list of series
|
||||||
study.seriesList.forEach(series => {
|
study.series.forEach(series => {
|
||||||
this.addSeries(new OHIFSeriesMetadata(series, study));
|
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
|
methods rely on the implementation of an unimplemented method. For example, the
|
||||||
method `getStringValue` from `InstanceMetadata` class, which has indeed been
|
method `getStringValue` from `InstanceMetadata` class, which has indeed been
|
||||||
implemented and is meant to retrieve a metadata value as a string, internally
|
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
|
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
|
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
|
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
|
structures, this very application would now benefit from all already implemented
|
||||||
methods.
|
methods.
|
||||||
@@ -99,12 +99,12 @@ The following code snippet tries to illustrate the idea:
|
|||||||
|
|
||||||
class InstanceMetadata {
|
class InstanceMetadata {
|
||||||
[ ... ]
|
[ ... ]
|
||||||
getRawValue(tagOrProperty, defaultValue) {
|
getTagValue(tagOrProperty, defaultValue) {
|
||||||
// Please implement this method in a specialized subclass...
|
// Please implement this method in a specialized subclass...
|
||||||
}
|
}
|
||||||
[ ... ]
|
[ ... ]
|
||||||
getStringValue(tagOrProperty, index, defaultValue) {
|
getStringValue(tagOrProperty, index, defaultValue) {
|
||||||
let rawValue = this.getRawValue(tagOrProperty, '');
|
let rawValue = this.getTagValue(tagOrProperty, '');
|
||||||
// parse the returned value into a string...
|
// parse the returned value into a string...
|
||||||
[ ... ]
|
[ ... ]
|
||||||
return stringValue;
|
return stringValue;
|
||||||
@@ -117,11 +117,11 @@ class InstanceMetadata {
|
|||||||
class MyFancyAppInstanceMetadata extends InstanceMetadata {
|
class MyFancyAppInstanceMetadata extends InstanceMetadata {
|
||||||
// Overriding this method will make all methods implemented in the super class
|
// Overriding this method will make all methods implemented in the super class
|
||||||
// that rely on it to be immediately available...
|
// that rely on it to be immediately available...
|
||||||
getRawValue(tagOrProperty, defaultValue) {
|
getTagValue(tagOrProperty, defaultValue) {
|
||||||
let rawValue;
|
let tagValue;
|
||||||
// retrieve raw value from internal data structures...
|
// 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
|
if (sopInstaceMetadata instanceof InstanceMetadata) { // also true
|
||||||
// this code will also be executed...
|
// 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)
|
// "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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sopClassUids = getSopClassUids(series);
|
const sopClassUIDs = getSopClassUIDs(series);
|
||||||
|
|
||||||
if (sopClassHandlerModules && sopClassHandlerModules.length > 0) {
|
if (sopClassHandlerModules && sopClassHandlerModules.length > 0) {
|
||||||
const displaySet = _getDisplaySetFromSopClassModule(
|
const displaySet = _getDisplaySetFromSopClassModule(
|
||||||
sopClassHandlerModules,
|
sopClassHandlerModules,
|
||||||
series,
|
series,
|
||||||
study,
|
study,
|
||||||
sopClassUids
|
sopClassUIDs
|
||||||
);
|
);
|
||||||
if (displaySet) {
|
if (displaySet) {
|
||||||
displaySet.sopClassModule = true;
|
displaySet.sopClassModule = true;
|
||||||
@@ -135,10 +135,10 @@ export class StudyMetadata extends Metadata {
|
|||||||
// series into another display set.
|
// series into another display set.
|
||||||
const stackableInstances = [];
|
const stackableInstances = [];
|
||||||
series.forEachInstance(instance => {
|
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 (
|
if (
|
||||||
!isImage(instance.getRawValue('x00080016')) &&
|
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||||
!instance.getRawValue('x00280010')
|
!instance.getTagValue('Rows')
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,23 +149,23 @@ export class StudyMetadata extends Metadata {
|
|||||||
displaySet = makeDisplaySet(series, [instance]);
|
displaySet = makeDisplaySet(series, [instance]);
|
||||||
|
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
isClip: true,
|
isClip: true,
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid for drag/drop purposes
|
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
||||||
numImageFrames: instance.getRawValue('x00280008'), // Override the default value of instances.length
|
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
||||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||||
});
|
});
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
} else if (isSingleImageModality(instance.modality)) {
|
} else if (isSingleImageModality(instance.Modality)) {
|
||||||
displaySet = makeDisplaySet(series, [instance]);
|
displaySet = makeDisplaySet(series, [instance]);
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid
|
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||||
});
|
});
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
} else {
|
} else {
|
||||||
@@ -175,9 +175,9 @@ export class StudyMetadata extends Metadata {
|
|||||||
|
|
||||||
if (stackableInstances.length) {
|
if (stackableInstances.length) {
|
||||||
const displaySet = makeDisplaySet(series, stackableInstances);
|
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||||
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
displaySet.setAttribute('StudyInstanceUID', study.getStudyInstanceUID());
|
||||||
displaySet.setAttributes({
|
displaySet.setAttributes({
|
||||||
sopClassUids,
|
sopClassUIDs,
|
||||||
});
|
});
|
||||||
displaySets.push(displaySet);
|
displaySets.push(displaySet);
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
* needs series sorted by the same criteria used for sorting display sets.
|
* needs series sorted by the same criteria used for sorting display sets.
|
||||||
*/
|
*/
|
||||||
sortSeriesByDisplaySets() {
|
sortSeriesByDisplaySets() {
|
||||||
// Object for mapping display sets' index by seriesInstanceUid
|
// Object for mapping display sets' index by SeriesInstanceUID
|
||||||
const displaySetsMapping = {};
|
const displaySetsMapping = {};
|
||||||
|
|
||||||
// Loop through each display set to create the mapping
|
// 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
|
// In case of multiframe studies, just get the first index occurence
|
||||||
if (displaySetsMapping[displaySet.seriesInstanceUid] === void 0) {
|
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
|
||||||
displaySetsMapping[displaySet.seriesInstanceUid] = index;
|
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -581,8 +581,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
const dwc = api.DICOMwebClient;
|
const dwc = api.DICOMwebClient;
|
||||||
|
|
||||||
const isMultiFrame = instance => {
|
const isMultiFrame = instance => {
|
||||||
// NumberOfFrames (0028,0008)
|
return instance.getTagValue('NumberOfFrames') > 1;
|
||||||
return instance.getRawValue('x00280008') > 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeDisplaySet = (series, instances) => {
|
const makeDisplaySet = (series, instances) => {
|
||||||
@@ -592,15 +591,15 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
|
|
||||||
// set appropriate attributes to image set...
|
// set appropriate attributes to image set...
|
||||||
imageSet.setAttributes({
|
imageSet.setAttributes({
|
||||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||||
seriesDate: seriesData.seriesDate,
|
SeriesDate: seriesData.SeriesDate,
|
||||||
seriesTime: seriesData.seriesTime,
|
SeriesTime: seriesData.SeriesTime,
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
seriesNumber: instance.getRawValue('x00200011'),
|
SeriesNumber: instance.getTagValue('SeriesNumber'),
|
||||||
seriesDescription: instance.getRawValue('x0008103e'),
|
SeriesDescription: instance.getTagValue('SeriesDescription'),
|
||||||
numImageFrames: instances.length,
|
numImageFrames: instances.length,
|
||||||
frameRate: instance.getRawValue('x00181063'),
|
frameRate: instance.getTagValue('FrameTime'),
|
||||||
modality: instance.getRawValue('x00080060'),
|
Modality: instance.getTagValue('Modality'),
|
||||||
isMultiFrame: isMultiFrame(instance),
|
isMultiFrame: isMultiFrame(instance),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -610,19 +609,19 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
imageSet.sortBy((a, b) => {
|
imageSet.sortBy((a, b) => {
|
||||||
// Sort by InstanceNumber (0020,0013)
|
// Sort by InstanceNumber (0020,0013)
|
||||||
return (
|
return (
|
||||||
(parseInt(a.getRawValue('x00200013', 0)) || 0) -
|
(parseInt(a.getTagValue('InstanceNumber', 0)) || 0) -
|
||||||
(parseInt(b.getRawValue('x00200013', 0)) || 0)
|
(parseInt(b.getTagValue('InstanceNumber', 0)) || 0)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the first image instance number (after sorted)
|
// Include the first image instance number (after sorted)
|
||||||
imageSet.setAttribute(
|
imageSet.setAttribute(
|
||||||
'instanceNumber',
|
'InstanceNumber',
|
||||||
imageSet.getImage(0).getRawValue('x00200013')
|
imageSet.getImage(0).getTagValue('InstanceNumber')
|
||||||
);
|
);
|
||||||
|
|
||||||
const isReconstructable = isDisplaySetReconstructable(series, instances);
|
const isReconstructable = isDisplaySetReconstructable(instances);
|
||||||
|
|
||||||
imageSet.isReconstructable = isReconstructable.value;
|
imageSet.isReconstructable = isReconstructable.value;
|
||||||
|
|
||||||
@@ -635,57 +634,57 @@ const makeDisplaySet = (series, instances) => {
|
|||||||
return imageSet;
|
return imageSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSingleImageModality = modality => {
|
const isSingleImageModality = Modality => {
|
||||||
return modality === 'CR' || modality === 'MG' || modality === 'DX';
|
return Modality === 'CR' || Modality === 'MG' || Modality === 'DX';
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSopClassUids(series) {
|
function getSopClassUIDs(series) {
|
||||||
const uniqueSopClassUidsInSeries = new Set();
|
const uniqueSopClassUIDsInSeries = new Set();
|
||||||
series.forEachInstance(instance => {
|
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
|
* @private
|
||||||
* @param {SeriesMetadata} series
|
* @param {SeriesMetadata} series
|
||||||
* @param {StudyMetadata} study
|
* @param {StudyMetadata} study
|
||||||
* @param {string[]} sopClassUids
|
* @param {string[]} sopClassUIDs
|
||||||
*/
|
*/
|
||||||
function _getDisplaySetFromSopClassModule(
|
function _getDisplaySetFromSopClassModule(
|
||||||
sopClassHandlerExtensions, // TODO: Update Usage
|
sopClassHandlerExtensions, // TODO: Update Usage
|
||||||
series,
|
series,
|
||||||
study,
|
study,
|
||||||
sopClassUids
|
sopClassUIDs
|
||||||
) {
|
) {
|
||||||
// TODO: For now only use the plugins if all instances have the same sopClassUid
|
// TODO: For now only use the plugins if all instances have the same SOPClassUID
|
||||||
if (sopClassUids.length !== 1) {
|
if (sopClassUIDs.length !== 1) {
|
||||||
console.warn(
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sopClassUid = sopClassUids[0];
|
const SOPClassUID = sopClassUIDs[0];
|
||||||
const sopClassHandlerModules = sopClassHandlerExtensions.map(extension => {
|
const sopClassHandlerModules = sopClassHandlerExtensions.map(extension => {
|
||||||
return extension.module;
|
return extension.module;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlersForSopClassUid = sopClassHandlerModules.filter(module => {
|
const handlersForSopClassUID = sopClassHandlerModules.filter(module => {
|
||||||
return module.sopClassUids.includes(sopClassUid);
|
return module.sopClassUIDs.includes(SOPClassUID);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Sort by something, so we can determine which plugin to use
|
// TODO: Sort by something, so we can determine which plugin to use
|
||||||
if (!handlersForSopClassUid || !handlersForSopClassUid.length) {
|
if (!handlersForSopClassUID || !handlersForSopClassUID.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugin = handlersForSopClassUid[0];
|
const plugin = handlersForSopClassUID[0];
|
||||||
const headers = DICOMWeb.getAuthorizationHeader();
|
const headers = DICOMWeb.getAuthorizationHeader();
|
||||||
const dicomWebClient = new dwc({
|
const dicomWebClient = new dwc({
|
||||||
url: study.getData().wadoRoot,
|
url: study.getData().wadoRoot,
|
||||||
@@ -698,15 +697,15 @@ function _getDisplaySetFromSopClassModule(
|
|||||||
dicomWebClient,
|
dicomWebClient,
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
if (displaySet && !displaySet.modality) {
|
if (displaySet && !displaySet.Modality) {
|
||||||
const instance = series.getFirstInstance();
|
const instance = series.getFirstInstance();
|
||||||
displaySet.modality = instance.getRawValue('x00080060');
|
displaySet.Modality = instance.getTagValue('Modality');
|
||||||
}
|
}
|
||||||
return displaySet;
|
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 like SEG, KO or PR are grouped in the end of the list) and then by
|
||||||
* series number:
|
* series number:
|
||||||
*
|
*
|
||||||
@@ -724,8 +723,8 @@ function _getDisplaySetFromSopClassModule(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function seriesSortingCriteria(a, b) {
|
function seriesSortingCriteria(a, b) {
|
||||||
const isLowPriorityA = isLowPriorityModality(a.modality);
|
const isLowPriorityA = isLowPriorityModality(a.Modality);
|
||||||
const isLowPriorityB = isLowPriorityModality(b.modality);
|
const isLowPriorityB = isLowPriorityModality(b.Modality);
|
||||||
if (!isLowPriorityA && isLowPriorityB) {
|
if (!isLowPriorityA && isLowPriorityB) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -742,7 +741,7 @@ function seriesSortingCriteria(a, b) {
|
|||||||
*/
|
*/
|
||||||
function sortBySeriesNumber(a, b) {
|
function sortBySeriesNumber(a, b) {
|
||||||
const seriesNumberAIsGreaterOrUndefined =
|
const seriesNumberAIsGreaterOrUndefined =
|
||||||
a.seriesNumber > b.seriesNumber || (!a.seriesNumber && b.seriesNumber);
|
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
|
||||||
|
|
||||||
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
|
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 { OHIFStudyMetadata } from './OHIFStudyMetadata';
|
||||||
import { SeriesMetadata } from './SeriesMetadata';
|
import { SeriesMetadata } from './SeriesMetadata';
|
||||||
import { StudyMetadata } from './StudyMetadata';
|
import { StudyMetadata } from './StudyMetadata';
|
||||||
import { StudySummary } from './StudySummary';
|
|
||||||
import { WadoRsMetaDataBuilder } from './WadoRsMetaDataBuilder';
|
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
Metadata,
|
Metadata,
|
||||||
StudySummary,
|
|
||||||
WadoRsMetaDataBuilder,
|
|
||||||
StudyMetadata,
|
StudyMetadata,
|
||||||
SeriesMetadata,
|
SeriesMetadata,
|
||||||
InstanceMetadata,
|
InstanceMetadata,
|
||||||
@@ -22,8 +18,6 @@ const metadata = {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Metadata,
|
Metadata,
|
||||||
StudySummary,
|
|
||||||
WadoRsMetaDataBuilder,
|
|
||||||
StudyMetadata,
|
StudyMetadata,
|
||||||
SeriesMetadata,
|
SeriesMetadata,
|
||||||
InstanceMetadata,
|
InstanceMetadata,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetadataProvider from './classes/MetadataProvider';
|
import metadataProvider from './classes/MetadataProvider';
|
||||||
import {
|
import {
|
||||||
getBoundingBox,
|
getBoundingBox,
|
||||||
pixelToPage,
|
pixelToPage,
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from './lib/cornerstone.js';
|
} from './lib/cornerstone.js';
|
||||||
|
|
||||||
const cornerstone = {
|
const cornerstone = {
|
||||||
MetadataProvider,
|
metadataProvider,
|
||||||
getBoundingBox,
|
getBoundingBox,
|
||||||
pixelToPage,
|
pixelToPage,
|
||||||
repositionTextBox,
|
repositionTextBox,
|
||||||
|
|||||||
@@ -6,24 +6,19 @@ import { CustomAttributeRetrievalCallbacks } from './customAttributes';
|
|||||||
/**
|
/**
|
||||||
* Import Constants
|
* Import Constants
|
||||||
*/
|
*/
|
||||||
const { StudySummary, InstanceMetadata } = metadata;
|
const { InstanceMetadata } = metadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match a Metadata instance against rules using Validate.js for validation.
|
* 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
|
* @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)
|
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
||||||
*/
|
*/
|
||||||
const match = (metadataInstance, rules) => {
|
const match = (metadataInstance, rules) => {
|
||||||
// Make sure the supplied data is valid.
|
// Make sure the supplied data is valid.
|
||||||
if (
|
if (!(metadataInstance instanceof InstanceMetadata)) {
|
||||||
!(
|
|
||||||
metadataInstance instanceof StudySummary ||
|
|
||||||
metadataInstance instanceof InstanceMetadata
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new OHIFError(
|
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
|
* Import Constants
|
||||||
*/
|
*/
|
||||||
const { StudyMetadata, InstanceMetadata, StudySummary } = metadata;
|
const { StudyMetadata, InstanceMetadata } = metadata;
|
||||||
|
|
||||||
// Useful constants
|
// Useful constants
|
||||||
const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue';
|
const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue';
|
||||||
@@ -298,10 +298,7 @@ export default class ProtocolEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Invalid data
|
// Invalid data
|
||||||
if (
|
if (!priorStudy instanceof StudyMetadata) {
|
||||||
!(priorStudy instanceof StudyMetadata) &&
|
|
||||||
!(priorStudy instanceof StudySummary)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,11 +382,9 @@ export default class ProtocolEngine {
|
|||||||
// This tests to make sure there is actually image data in this instance
|
// This tests to make sure there is actually image data in this instance
|
||||||
// TODO: Change this when we add PDF and MPEG support
|
// TODO: Change this when we add PDF and MPEG support
|
||||||
// See https://ohiforg.atlassian.net/browse/LT-227
|
// See https://ohiforg.atlassian.net/browse/LT-227
|
||||||
// sopClassUid = x00080016
|
|
||||||
// rows = x00280010
|
|
||||||
if (
|
if (
|
||||||
!isImage(instance.getTagValue('x00080016')) &&
|
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||||
!instance.getTagValue('x00280010')
|
!instance.getTagValue('Rows')
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -436,19 +431,19 @@ export default class ProtocolEngine {
|
|||||||
const currentSOPInstanceUID = instance.getSOPInstanceUID();
|
const currentSOPInstanceUID = instance.getSOPInstanceUID();
|
||||||
|
|
||||||
const imageDetails = {
|
const imageDetails = {
|
||||||
studyInstanceUid: study.getStudyInstanceUID(),
|
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||||
sopInstanceUid: currentSOPInstanceUID,
|
SOPInstanceUID: currentSOPInstanceUID,
|
||||||
currentImageIdIndex: index,
|
currentImageIdIndex: index,
|
||||||
matchingScore: totalMatchScore,
|
matchingScore: totalMatchScore,
|
||||||
matchDetails: matchDetails,
|
matchDetails: matchDetails,
|
||||||
sortingInfo: {
|
sortingInfo: {
|
||||||
score: totalMatchScore,
|
score: totalMatchScore,
|
||||||
study:
|
study:
|
||||||
instance.getTagValue('x00080020') +
|
instance.getTagValue('StudyDate') +
|
||||||
instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
instance.getTagValue('StudyTime'),
|
||||||
series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
series: parseInt(instance.getTagValue('SeriesNumber')), // TODO: change for seriesDateTime
|
||||||
instance: parseInt(instance.getTagValue('x00200013')), // TODO: change for acquisitionTime InstanceNumber = x00200013
|
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 the instance was found, set the displaySet ID
|
||||||
if (displaySet) {
|
if (displaySet) {
|
||||||
imageDetails.displaySetInstanceUid = displaySet.getUID();
|
imageDetails.displaySetInstanceUID = displaySet.getUID();
|
||||||
imageDetails.imageId = instance.getImageId();
|
imageDetails.imageId = instance.getImageId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,25 +649,25 @@ export default class ProtocolEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentMatch && currentMatch.imageId) {
|
if (currentMatch && currentMatch.imageId) {
|
||||||
currentViewportData.studyInstanceUid = currentMatch.studyInstanceUid;
|
currentViewportData.StudyInstanceUID = currentMatch.StudyInstanceUID;
|
||||||
currentViewportData.seriesInstanceUid = currentMatch.seriesInstanceUid;
|
currentViewportData.SeriesInstanceUID = currentMatch.SeriesInstanceUID;
|
||||||
currentViewportData.sopInstanceUid = currentMatch.sopInstanceUid;
|
currentViewportData.SOPInstanceUID = currentMatch.SOPInstanceUID;
|
||||||
currentViewportData.currentImageIdIndex =
|
currentViewportData.currentImageIdIndex =
|
||||||
currentMatch.currentImageIdIndex;
|
currentMatch.currentImageIdIndex;
|
||||||
currentViewportData.displaySetInstanceUid =
|
currentViewportData.displaySetInstanceUID =
|
||||||
currentMatch.displaySetInstanceUid;
|
currentMatch.displaySetInstanceUID;
|
||||||
currentViewportData.imageId = currentMatch.imageId;
|
currentViewportData.imageId = currentMatch.imageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO Why should we throw an exception when a best match is not found? This was aborting the whole process.
|
// @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?');
|
// throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
viewportData.push(currentViewportData);
|
viewportData.push(currentViewportData);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setLayout(layoutProps.rows, layoutProps.columns);
|
this.setLayout(layoutProps.Rows, layoutProps.Columns);
|
||||||
|
|
||||||
if (typeof this.options.setViewportSpecificData !== 'function') {
|
if (typeof this.options.setViewportSpecificData !== 'function') {
|
||||||
log.error(
|
log.error(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const EQUALS_REGEXP = /^equals$/;
|
|||||||
/**
|
/**
|
||||||
* This Class represents a Rule to be evaluated given a set of attributes
|
* This Class represents a Rule to be evaluated given a set of attributes
|
||||||
* Rules have:
|
* Rules have:
|
||||||
* - An attribute (e.g. 'seriesDescription')
|
* - An attribute (e.g. 'SeriesDescription')
|
||||||
* - A constraint Object, in the form required by Validate.js:
|
* - A constraint Object, in the form required by Validate.js:
|
||||||
*
|
*
|
||||||
* rule.constraint = {
|
* rule.constraint = {
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export default class ViewportStructure {
|
|||||||
// Viewport structure can be updated later when we build more complex display layouts
|
// Viewport structure can be updated later when we build more complex display layouts
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case 'grid':
|
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
|
// 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;
|
protocol.locked = true;
|
||||||
|
|
||||||
const oneByOne = new ViewportStructure('grid', {
|
const oneByOne = new ViewportStructure('grid', {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 1,
|
Columns: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewport = new Viewport();
|
const viewport = new Viewport();
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ function getMRTwoByTwoTest() {
|
|||||||
proto.locked = true;
|
proto.locked = true;
|
||||||
// Use http://localhost:3000/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78
|
// Use http://localhost:3000/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78
|
||||||
|
|
||||||
var studyInstanceUid = new ProtocolMatchingRule(
|
var StudyInstanceUID = new ProtocolMatchingRule(
|
||||||
'studyInstanceUid',
|
'StudyInstanceUID',
|
||||||
{
|
{
|
||||||
equals: {
|
equals: {
|
||||||
value: '1.2.840.113619.2.5.1762583153.215519.978957063.78',
|
value: '1.2.840.113619.2.5.1762583153.215519.978957063.78',
|
||||||
@@ -24,30 +24,30 @@ function getMRTwoByTwoTest() {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
proto.addProtocolMatchingRule(studyInstanceUid);
|
proto.addProtocolMatchingRule(StudyInstanceUID);
|
||||||
|
|
||||||
var oneByTwo = new ViewportStructure('grid', {
|
var oneByTwo = new ViewportStructure('grid', {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stage 1
|
// Stage 1
|
||||||
var left = new Viewport();
|
var left = new Viewport();
|
||||||
var right = new Viewport();
|
var right = new Viewport();
|
||||||
|
|
||||||
var firstSeries = new SeriesMatchingRule('seriesNumber', {
|
var firstSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||||
equals: {
|
equals: {
|
||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var secondSeries = new SeriesMatchingRule('seriesNumber', {
|
var secondSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||||
equals: {
|
equals: {
|
||||||
value: 2,
|
value: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var thirdImage = new ImageMatchingRule('instanceNumber', {
|
var thirdImage = new ImageMatchingRule('InstanceNumber', {
|
||||||
equals: {
|
equals: {
|
||||||
value: 3,
|
value: 3,
|
||||||
},
|
},
|
||||||
@@ -67,19 +67,19 @@ function getMRTwoByTwoTest() {
|
|||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
var twoByOne = new ViewportStructure('grid', {
|
var twoByOne = new ViewportStructure('grid', {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 1,
|
Columns: 1,
|
||||||
});
|
});
|
||||||
var left2 = new Viewport();
|
var left2 = new Viewport();
|
||||||
var right2 = new Viewport();
|
var right2 = new Viewport();
|
||||||
|
|
||||||
var fourthSeries = new SeriesMatchingRule('seriesNumber', {
|
var fourthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||||
equals: {
|
equals: {
|
||||||
value: 4,
|
value: 4,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var fifthSeries = new SeriesMatchingRule('seriesNumber', {
|
var fifthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||||
equals: {
|
equals: {
|
||||||
value: 5,
|
value: 5,
|
||||||
},
|
},
|
||||||
@@ -133,8 +133,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -195,8 +195,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -257,8 +257,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -402,8 +402,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -464,8 +464,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -548,8 +548,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -737,8 +737,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 1,
|
Columns: 1,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -770,8 +770,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -931,8 +931,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -993,8 +993,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -1044,8 +1044,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -1127,8 +1127,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -1190,8 +1190,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 1,
|
Rows: 1,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -1275,8 +1275,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
@@ -1386,8 +1386,8 @@ function getDemoProtocols() {
|
|||||||
viewportStructure: {
|
viewportStructure: {
|
||||||
type: 'grid',
|
type: 'grid',
|
||||||
properties: {
|
properties: {
|
||||||
rows: 2,
|
Rows: 2,
|
||||||
columns: 2,
|
Columns: 2,
|
||||||
},
|
},
|
||||||
layoutTemplateName: 'gridLayout',
|
layoutTemplateName: 'gridLayout',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import string from './string.js';
|
|||||||
import studies from './studies/';
|
import studies from './studies/';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
import user from './user.js';
|
import user from './user.js';
|
||||||
import utils from './utils/';
|
import utils, { hotkeys } from './utils/';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UINotificationService,
|
UINotificationService,
|
||||||
@@ -36,6 +36,7 @@ const OHIF = {
|
|||||||
ServicesManager,
|
ServicesManager,
|
||||||
//
|
//
|
||||||
utils,
|
utils,
|
||||||
|
hotkeys,
|
||||||
studies,
|
studies,
|
||||||
redux,
|
redux,
|
||||||
classes,
|
classes,
|
||||||
@@ -68,6 +69,7 @@ export {
|
|||||||
ServicesManager,
|
ServicesManager,
|
||||||
//
|
//
|
||||||
utils,
|
utils,
|
||||||
|
hotkeys,
|
||||||
studies,
|
studies,
|
||||||
redux,
|
redux,
|
||||||
classes,
|
classes,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe('Top level exports', () => {
|
|||||||
'MeasurementService',
|
'MeasurementService',
|
||||||
//
|
//
|
||||||
'utils',
|
'utils',
|
||||||
|
'hotkeys',
|
||||||
'studies',
|
'studies',
|
||||||
'redux',
|
'redux',
|
||||||
'classes',
|
'classes',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function getBoundingBox(context, textLines, x, y, options) {
|
|||||||
// Get the text width in the current font
|
// Get the text width in the current font
|
||||||
const width = context.measureText(text).width;
|
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);
|
maxWidth = Math.max(maxWidth, width);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import guid from '../../utils/guid';
|
|||||||
import studyMetadataManager from '../../utils/studyMetadataManager';
|
import studyMetadataManager from '../../utils/studyMetadataManager';
|
||||||
import { measurementApiDefaultConfig } from './../configuration.js';
|
import { measurementApiDefaultConfig } from './../configuration.js';
|
||||||
|
|
||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
...measurementApiDefaultConfig
|
...measurementApiDefaultConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class MeasurementApi {
|
export default class MeasurementApi {
|
||||||
@@ -99,8 +98,8 @@ export default class MeasurementApi {
|
|||||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||||
|
|
||||||
// Stop here if the metadata for the measurement's study is not loaded yet
|
// Stop here if the metadata for the measurement's study is not loaded yet
|
||||||
const { studyInstanceUid } = measurement;
|
const { StudyInstanceUID } = measurement;
|
||||||
const metadata = studyMetadataManager.get(studyInstanceUid);
|
const metadata = studyMetadataManager.get(StudyInstanceUID);
|
||||||
if (!metadata) return;
|
if (!metadata) return;
|
||||||
|
|
||||||
// Iterate each child tool if the current tool has children
|
// 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));
|
this.options.onMeasurementsUpdated(Object.assign({}, this.tools));
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveMeasurements(patientId, timepointIds) {
|
retrieveMeasurements(PatientID, timepointIds) {
|
||||||
const retrievalFn = configuration.dataExchange.retrieve;
|
const retrievalFn = configuration.dataExchange.retrieve;
|
||||||
const { server } = configuration;
|
const { server } = configuration;
|
||||||
if (typeof retrievalFn !== 'function') {
|
if (typeof retrievalFn !== 'function') {
|
||||||
@@ -297,9 +296,9 @@ export default class MeasurementApi {
|
|||||||
: null;
|
: null;
|
||||||
const timepoints = this.timepointApi.all(timepointFilter);
|
const timepoints = this.timepointApi.all(timepointFilter);
|
||||||
const timepointIds = timepoints.map(t => t.timepointId);
|
const timepointIds = timepoints.map(t => t.timepointId);
|
||||||
const patientId = timepoints[0].patientId;
|
const PatientID = timepoints[0].PatientID;
|
||||||
const filter = {
|
const filter = {
|
||||||
patientId,
|
PatientID,
|
||||||
timepointIds,
|
timepointIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -576,17 +575,17 @@ export default class MeasurementApi {
|
|||||||
|
|
||||||
const toolGroupId = this.toolsGroupsMap[measurementData.toolType];
|
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
|
// by dependant applications. Here we should use the location attribute instead of the uid
|
||||||
let filter;
|
let filter;
|
||||||
const uid =
|
const uid =
|
||||||
measurementData.additionalData &&
|
measurementData.additionalData &&
|
||||||
measurementData.additionalData.TrialPatientLocationUid;
|
measurementData.additionalData.TrialPatientLocationUID;
|
||||||
if (uid) {
|
if (uid) {
|
||||||
filter = tool =>
|
filter = tool =>
|
||||||
tool._id !== measurementData._id &&
|
tool._id !== measurementData._id &&
|
||||||
tool.additionalData &&
|
tool.additionalData &&
|
||||||
tool.additionalData.TrialPatientLocationUid === uid;
|
tool.additionalData.TrialPatientLocationUID === uid;
|
||||||
} else {
|
} else {
|
||||||
filter = tool =>
|
filter = tool =>
|
||||||
tool._id !== measurementData._id &&
|
tool._id !== measurementData._id &&
|
||||||
@@ -691,8 +690,8 @@ export default class MeasurementApi {
|
|||||||
|
|
||||||
// Get the timepoint
|
// Get the timepoint
|
||||||
let timepoint;
|
let timepoint;
|
||||||
if (measurement.studyInstanceUid) {
|
if (measurement.StudyInstanceUID) {
|
||||||
timepoint = this.timepointApi.study(measurement.studyInstanceUid)[0];
|
timepoint = this.timepointApi.study(measurement.StudyInstanceUID)[0];
|
||||||
} else {
|
} else {
|
||||||
const { timepointId } = measurement;
|
const { timepointId } = measurement;
|
||||||
timepoint = this.timepointApi.timepoints.find(
|
timepoint = this.timepointApi.timepoints.find(
|
||||||
@@ -760,7 +759,7 @@ export default class MeasurementApi {
|
|||||||
measurement.lesionNamingNumber = found.lesionNamingNumber;
|
measurement.lesionNamingNumber = found.lesionNamingNumber;
|
||||||
measurement.measurementNumber = found.measurementNumber;
|
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
|
// by dependant applications
|
||||||
|
|
||||||
// Change the update object to set the same number, additionalData,
|
// Change the update object to set the same number, additionalData,
|
||||||
@@ -768,8 +767,8 @@ export default class MeasurementApi {
|
|||||||
updateObject.lesionNamingNumber = found.lesionNamingNumber;
|
updateObject.lesionNamingNumber = found.lesionNamingNumber;
|
||||||
updateObject.measurementNumber = found.measurementNumber;
|
updateObject.measurementNumber = found.measurementNumber;
|
||||||
updateObject.additionalData = measurement.additionalData || {};
|
updateObject.additionalData = measurement.additionalData || {};
|
||||||
updateObject.additionalData.TrialPatientLocationUid =
|
updateObject.additionalData.TrialPatientLocationUID =
|
||||||
found.additionalData && found.additionalData.TrialPatientLocationUid;
|
found.additionalData && found.additionalData.TrialPatientLocationUID;
|
||||||
updateObject.location = found.location;
|
updateObject.location = found.location;
|
||||||
updateObject.label = found.label;
|
updateObject.label = found.label;
|
||||||
updateObject.description = found.description;
|
updateObject.description = found.description;
|
||||||
@@ -807,7 +806,7 @@ export default class MeasurementApi {
|
|||||||
toolId: toolType,
|
toolId: toolType,
|
||||||
toolItemId: addedMeasurement._id,
|
toolItemId: addedMeasurement._id,
|
||||||
timepointId: timepoint.timepointId,
|
timepointId: timepoint.timepointId,
|
||||||
studyInstanceUid: addedMeasurement.studyInstanceUid,
|
StudyInstanceUID: addedMeasurement.StudyInstanceUID,
|
||||||
createdAt: addedMeasurement.createdAt,
|
createdAt: addedMeasurement.createdAt,
|
||||||
lesionNamingNumber: addedMeasurement.lesionNamingNumber,
|
lesionNamingNumber: addedMeasurement.lesionNamingNumber,
|
||||||
measurementNumber: addedMeasurement.measurementNumber,
|
measurementNumber: addedMeasurement.measurementNumber,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default class TimepointApi {
|
|||||||
});
|
});
|
||||||
const filteredTimepoints = sortedTimepoints.find(
|
const filteredTimepoints = sortedTimepoints.find(
|
||||||
tp =>
|
tp =>
|
||||||
tp.patientId === timepoint.patientId &&
|
tp.PatientID === timepoint.PatientID &&
|
||||||
tp.timepointType === timepoint.timepointType
|
tp.timepointType === timepoint.timepointType
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -129,14 +129,14 @@ export default class TimepointApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disassociateStudy(timepointIds, studyInstanceUid) {
|
disassociateStudy(timepointIds, StudyInstanceUID) {
|
||||||
const disassociateFn = configuration.dataExchange.disassociate;
|
const disassociateFn = configuration.dataExchange.disassociate;
|
||||||
if (typeof disassociateFn !== 'function') {
|
if (typeof disassociateFn !== 'function') {
|
||||||
log.error('Study disassociate function has not been configured.');
|
log.error('Study disassociate function has not been configured.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
disassociateFn(timepointIds, studyInstanceUid).then(() => {
|
disassociateFn(timepointIds, StudyInstanceUID).then(() => {
|
||||||
log.info('Disassociation completed');
|
log.info('Disassociation completed');
|
||||||
|
|
||||||
this.timepoints = [];
|
this.timepoints = [];
|
||||||
@@ -510,9 +510,9 @@ export default class TimepointApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return only the timepoints for the given study
|
// Return only the timepoints for the given study
|
||||||
study(studyInstanceUid) {
|
study(StudyInstanceUID) {
|
||||||
return this.all().filter(timepoint =>
|
return this.all().filter(timepoint =>
|
||||||
timepoint.studyInstanceUids.includes(studyInstanceUid)
|
timepoint.studyInstanceUIDs.includes(StudyInstanceUID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ class ConformanceCriteria {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStudy(studyInstanceUid) {
|
loadStudy(StudyInstanceUID) {
|
||||||
if (typeof this.options.loadStudy !== 'function') {
|
if (typeof this.options.loadStudy !== 'function') {
|
||||||
throw new Error('loadStudy callback is not defined');
|
throw new Error('loadStudy callback is not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.options.loadStudy(null, studyInstanceUid);
|
return this.options.loadStudy(null, StudyInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(trialCriteriaType) {
|
async validate(trialCriteriaType) {
|
||||||
@@ -181,7 +181,7 @@ class ConformanceCriteria {
|
|||||||
const measurements = this.measurementApi.fetch(measurementType);
|
const measurements = this.measurementApi.fetch(measurementType);
|
||||||
|
|
||||||
measurements.forEach(measurement => {
|
measurements.forEach(measurement => {
|
||||||
const { studyInstanceUid } = measurement;
|
const { StudyInstanceUID } = measurement;
|
||||||
|
|
||||||
const timepointId = measurement.timepointId;
|
const timepointId = measurement.timepointId;
|
||||||
const timepoint =
|
const timepoint =
|
||||||
@@ -195,7 +195,7 @@ class ConformanceCriteria {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = this.loadStudy(studyInstanceUid);
|
const promise = this.loadStudy(StudyInstanceUID);
|
||||||
promise.then(
|
promise.then(
|
||||||
studyMetadata => {
|
studyMetadata => {
|
||||||
data[measurementType].push({
|
data[measurementType].push({
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const MeasurementsLengthSchema = {
|
|||||||
},
|
},
|
||||||
isNodal: {
|
isNodal: {
|
||||||
label: 'Filter to evaluate only nodal or extranodal measurements',
|
label: 'Filter to evaluate only nodal or extranodal measurements',
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
label: 'Message to be displayed in case of nonconformity',
|
label: 'Message to be displayed in case of nonconformity',
|
||||||
@@ -119,19 +119,24 @@ export class MeasurementsLengthCriterion extends BaseCriterion {
|
|||||||
shortestDiameter = (child && child.shortestDiameter) || 0;
|
shortestDiameter = (child && child.shortestDiameter) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sliceThickness } = metadata;
|
const { SliceThickness } = metadata;
|
||||||
const modality = (metadata.getRawValue('x00080060') || '').toUpperCase();
|
|
||||||
|
|
||||||
// Stop here if the measurement does not match the modality and location filters
|
const Modality = metadata.getTagValue('Modality') || '';
|
||||||
if (typeof isNodal === 'boolean' && typeof options.isNodal === 'boolean' && options.isNodal !== isNodal)
|
|
||||||
|
// Stop here if the measurement does not match the Modality and location filters
|
||||||
|
if (
|
||||||
|
typeof isNodal === 'boolean' &&
|
||||||
|
typeof options.isNodal === 'boolean' &&
|
||||||
|
options.isNodal !== isNodal
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
if (options.locationIn && options.locationIn.indexOf(location) === -1)
|
if (options.locationIn && options.locationIn.indexOf(location) === -1)
|
||||||
return;
|
return;
|
||||||
if (options.modalityIn && options.modalityIn.indexOf(modality) === -1)
|
if (options.modalityIn && options.modalityIn.indexOf(Modality) === -1)
|
||||||
return;
|
return;
|
||||||
if (options.locationNotIn && options.locationNotIn.indexOf(location) > -1)
|
if (options.locationNotIn && options.locationNotIn.indexOf(location) > -1)
|
||||||
return;
|
return;
|
||||||
if (options.modalityNotIn && options.modalityNotIn.indexOf(modality) > -1)
|
if (options.modalityNotIn && options.modalityNotIn.indexOf(Modality) > -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Check the measurement length
|
// Check the measurement length
|
||||||
@@ -139,11 +144,11 @@ export class MeasurementsLengthCriterion extends BaseCriterion {
|
|||||||
(options.longAxis && longestDiameter < options.longAxis) ||
|
(options.longAxis && longestDiameter < options.longAxis) ||
|
||||||
(options.shortAxis && shortestDiameter < options.shortAxis) ||
|
(options.shortAxis && shortestDiameter < options.shortAxis) ||
|
||||||
(longMultiplier &&
|
(longMultiplier &&
|
||||||
!isNaN(sliceThickness) &&
|
!isNaN(SliceThickness) &&
|
||||||
longestDiameter < longMultiplier * sliceThickness) ||
|
longestDiameter < longMultiplier * SliceThickness) ||
|
||||||
(shortMultiplier &&
|
(shortMultiplier &&
|
||||||
!isNaN(sliceThickness) &&
|
!isNaN(SliceThickness) &&
|
||||||
shortestDiameter < shortMultiplier * sliceThickness);
|
shortestDiameter < shortMultiplier * SliceThickness);
|
||||||
|
|
||||||
// Mark this measurement as invalid if some of the checks have failed
|
// Mark this measurement as invalid if some of the checks have failed
|
||||||
if (failed) {
|
if (failed) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const ModalitySchema = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* ModalityCriteria
|
* ModalityCriteria
|
||||||
* Check if a modality is allowed or denied
|
* Check if a Modality is allowed or denied
|
||||||
* Options:
|
* Options:
|
||||||
* method (string): Specify if it\'s goinig to "allow" or "deny" the modalities
|
* method (string): Specify if it\'s goinig to "allow" or "deny" the modalities
|
||||||
* measurementTypes (string[]): List of measurement types that will be evaluated
|
* measurementTypes (string[]): List of measurement types that will be evaluated
|
||||||
@@ -56,16 +56,14 @@ export class ModalityCriterion extends BaseCriterion {
|
|||||||
|
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const { measurement, metadata } = item;
|
const { measurement, metadata } = item;
|
||||||
const modality = (
|
const Modality = metadata.getTagValue('Modality') || '';
|
||||||
metadata.getRawValue('x00080060') || ''
|
|
||||||
).toUpperCase();
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(validationMethod === 'allow' && !modalitiesSet.has(modality)) ||
|
(validationMethod === 'allow' && !modalitiesSet.has(Modality)) ||
|
||||||
(validationMethod === 'deny' && modalitiesSet.has(modality))
|
(validationMethod === 'deny' && modalitiesSet.has(Modality))
|
||||||
) {
|
) {
|
||||||
measurements.push(measurement);
|
measurements.push(measurement);
|
||||||
invalidModalities.add(modality);
|
invalidModalities.add(Modality);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -74,7 +72,7 @@ export class ModalityCriterion extends BaseCriterion {
|
|||||||
const uniqueModalities = Array.from(invalidModalities);
|
const uniqueModalities = Array.from(invalidModalities);
|
||||||
const uniqueModalitiesText = uniqueModalities.join(', ');
|
const uniqueModalitiesText = uniqueModalities.join(', ');
|
||||||
const modalityText =
|
const modalityText =
|
||||||
uniqueModalities.length > 1 ? 'modalities' : 'modality';
|
uniqueModalities.length > 1 ? 'modalities' : 'Modality';
|
||||||
|
|
||||||
message = `The ${modalityText} ${uniqueModalitiesText} should not be used as a method of measurement`;
|
message = `The ${modalityText} ${uniqueModalitiesText} should not be used as a method of measurement`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import log from '../log';
|
import log from '../log';
|
||||||
|
|
||||||
export const retrieveMeasurements = (patientId, timepointIds) => {
|
export const retrieveMeasurements = (PatientID, timepointIds) => {
|
||||||
log.error('retrieveMeasurements');
|
log.error('retrieveMeasurements');
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,7 @@ export const removeTimepoint = timepointId => {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disassociateStudy = (timepointIds, studyInstanceUid) => {
|
export const disassociateStudy = (timepointIds, StudyInstanceUID) => {
|
||||||
log.error('disassociateStudy');
|
log.error('disassociateStudy');
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
@@ -5,32 +5,30 @@ export default function(element) {
|
|||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
const imageId = enabledElement.image.imageId;
|
const imageId = enabledElement.image.imageId;
|
||||||
|
|
||||||
// Get studyInstanceUid & patientId
|
// Get StudyInstanceUID & PatientID
|
||||||
const study = cornerstone.metaData.get('study', imageId);
|
const {
|
||||||
const studyInstanceUid = study.studyInstanceUid;
|
StudyInstanceUID,
|
||||||
const patientId = study.patientId;
|
PatientID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
} = cornerstone.metaData.get('instance', imageId);
|
||||||
|
|
||||||
// Get seriesInstanceUid
|
const splitImageId = imageId.split('&frame');
|
||||||
const series = cornerstone.metaData.get('series', imageId);
|
const frameIndex =
|
||||||
const seriesInstanceUid = series.seriesInstanceUid;
|
splitImageId[1] !== undefined ? Number(splitImageId[1]) : 0;
|
||||||
|
|
||||||
// Get sopInstanceUid
|
|
||||||
const sopInstance = cornerstone.metaData.get('instance', imageId);
|
|
||||||
const sopInstanceUid = sopInstance.sopInstanceUid;
|
|
||||||
const frameIndex = sopInstance.frame || 0;
|
|
||||||
|
|
||||||
const imagePath = [
|
const imagePath = [
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
].join('_');
|
].join('_');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
patientId,
|
PatientID,
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
imagePath,
|
imagePath,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import studyMetadataManager from '../../utils/studyMetadataManager';
|
|||||||
|
|
||||||
export default function(imagePath, thumbnail = false) {
|
export default function(imagePath, thumbnail = false) {
|
||||||
const [
|
const [
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
seriesInstanceUid,
|
SeriesInstanceUID,
|
||||||
sopInstanceUid,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
] = imagePath.split('_');
|
] = imagePath.split('_');
|
||||||
const studyMetadata = studyMetadataManager.get(studyInstanceUid);
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||||
const series = studyMetadata.getSeriesByUID(seriesInstanceUid);
|
const series = studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||||
const instance = series.getInstanceByUID(sopInstanceUid);
|
const instance = series.getInstanceByUID(SOPInstanceUID);
|
||||||
return instance.getImageId(frameIndex, thumbnail);
|
return instance.getImageId(frameIndex, thumbnail);
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
|||||||
const parentMeasurement = collection.find(
|
const parentMeasurement = collection.find(
|
||||||
t =>
|
t =>
|
||||||
t.toolType === tool.parentTool &&
|
t.toolType === tool.parentTool &&
|
||||||
t.patientId === imageAttributes.patientId &&
|
t.PatientID === imageAttributes.PatientID &&
|
||||||
t[tool.attribute] === null
|
t[tool.attribute] === null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
|||||||
toolType: tool.parentTool,
|
toolType: tool.parentTool,
|
||||||
lesionNamingNumber: measurementData.lesionNamingNumber,
|
lesionNamingNumber: measurementData.lesionNamingNumber,
|
||||||
userId: user.getUserId(),
|
userId: user.getUserId(),
|
||||||
patientId: imageAttributes.patientId,
|
PatientID: imageAttributes.PatientID,
|
||||||
studyInstanceUid: imageAttributes.studyInstanceUid,
|
StudyInstanceUID: imageAttributes.StudyInstanceUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
measurement[tool.attribute] = Object.assign(
|
measurement[tool.attribute] = Object.assign(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let text = '';
|
let text = '';
|
||||||
if (data.rAngle) {
|
if (data.rAngle && !isNaN(data.rAngle)) {
|
||||||
text = data.rAngle.toFixed(2) + String.fromCharCode(parseInt('00B0', 16));
|
text = data.rAngle.toFixed(2) + String.fromCharCode(parseInt('00B0', 16));
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let meanValue = '';
|
let meanValue = '';
|
||||||
const { cachedStats } = data;
|
const { cachedStats } = data;
|
||||||
if (cachedStats && cachedStats.mean) {
|
if (cachedStats && cachedStats.mean && !isNaN(cachedStats.mean)) {
|
||||||
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
||||||
}
|
}
|
||||||
return meanValue;
|
return meanValue;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let meanValue = '';
|
let meanValue = '';
|
||||||
const { cachedStats } = data;
|
const { cachedStats } = data;
|
||||||
if (cachedStats && cachedStats.mean) {
|
if (cachedStats && cachedStats.mean && !isNaN(cachedStats.mean)) {
|
||||||
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
||||||
}
|
}
|
||||||
return meanValue;
|
return meanValue;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let meanValue = '';
|
let meanValue = '';
|
||||||
if (data.meanStdDev && data.meanStdDev.mean) {
|
if (data.meanStdDev && data.meanStdDev.mean && !isNaN(data.meanStdDev.mean)) {
|
||||||
meanValue = data.meanStdDev.mean.toFixed(2) + ' HU';
|
meanValue = data.meanStdDev.mean.toFixed(2) + ' HU';
|
||||||
}
|
}
|
||||||
return meanValue;
|
return meanValue;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let lengthValue = '';
|
let lengthValue = '';
|
||||||
if (data.length) {
|
if (data.length && !isNaN(data.length)) {
|
||||||
lengthValue = data.length.toFixed(2) + ' mm';
|
lengthValue = data.length.toFixed(2) + ' mm';
|
||||||
}
|
}
|
||||||
return lengthValue;
|
return lengthValue;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const displayFunction = data => {
|
const displayFunction = data => {
|
||||||
let meanValue = '';
|
let meanValue = '';
|
||||||
const { cachedStats } = data;
|
const { cachedStats } = data;
|
||||||
if (cachedStats && cachedStats.mean) {
|
if (cachedStats && cachedStats.mean && !isNaN(cachedStats.mean)) {
|
||||||
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
meanValue = cachedStats.mean.toFixed(2) + ' HU';
|
||||||
}
|
}
|
||||||
return meanValue;
|
return meanValue;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SET_VIEWPORT_ACTIVE,
|
SET_VIEWPORT_ACTIVE,
|
||||||
SET_VIEWPORT_LAYOUT,
|
SET_VIEWPORT_LAYOUT,
|
||||||
SET_VIEWPORT_LAYOUT_AND_DATA,
|
SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
|
SET_USER_PREFERENCES,
|
||||||
} from './constants/ActionTypes.js';
|
} from './constants/ActionTypes.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +90,7 @@ export const clearStudyLoadingProgress = progressId => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const setUserPreferences = state => ({
|
export const setUserPreferences = state => ({
|
||||||
type: 'SET_USER_PREFERENCES',
|
type: SET_USER_PREFERENCES,
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,9 +110,9 @@ export const setMeasurements = state => ({
|
|||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setStudyData = (studyInstanceUid, data) => ({
|
export const setStudyData = (StudyInstanceUID, data) => ({
|
||||||
type: 'SET_STUDY_DATA',
|
type: 'SET_STUDY_DATA',
|
||||||
studyInstanceUid,
|
StudyInstanceUID,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,20 +29,20 @@ describe('actions', () => {
|
|||||||
describe('viewport action creators', () => {
|
describe('viewport action creators', () => {
|
||||||
it('should create an action to set the viewport specific data', () => {
|
it('should create an action to set the viewport specific data', () => {
|
||||||
const viewportSpecificData = {
|
const viewportSpecificData = {
|
||||||
displaySetInstanceUid: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
displaySetInstanceUID: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
||||||
seriesDate: '20151026',
|
SeriesDate: '20151026',
|
||||||
seriesTime: '082611.370000',
|
SeriesTime: '082611.370000',
|
||||||
seriesInstanceUid:
|
SeriesInstanceUID:
|
||||||
'1.3.6.1.4.1.25403.345050719074.3824.20170126085406.5',
|
'1.3.6.1.4.1.25403.345050719074.3824.20170126085406.5',
|
||||||
seriesNumber: 2,
|
SeriesNumber: 2,
|
||||||
seriesDescription: 'Chest 3x3 Soft',
|
SeriesDescription: 'Chest 3x3 Soft',
|
||||||
numImageFrames: 126,
|
numImageFrames: 126,
|
||||||
modality: 'CT',
|
Modality: 'CT',
|
||||||
isMultiFrame: false,
|
isMultiFrame: false,
|
||||||
instanceNumber: 1,
|
InstanceNumber: 1,
|
||||||
studyInstanceUid:
|
StudyInstanceUID:
|
||||||
'1.3.6.1.4.1.25403.345050719074.3824.20170126085406.1',
|
'1.3.6.1.4.1.25403.345050719074.3824.20170126085406.1',
|
||||||
sopClassUids: ['1.2.840.10008.5.1.4.1.1.2'],
|
sopClassUIDs: ['1.2.840.10008.5.1.4.1.1.2'],
|
||||||
plugin: 'cornerstone',
|
plugin: 'cornerstone',
|
||||||
viewport: {
|
viewport: {
|
||||||
zoomScale: null,
|
zoomScale: null,
|
||||||
|
|||||||
@@ -20,3 +20,8 @@ export const SET_SERVERS = 'SET_SERVERS';
|
|||||||
* EXTENSIONS
|
* EXTENSIONS
|
||||||
*/
|
*/
|
||||||
export const SET_EXTENSION_DATA = 'SET_EXTENSION_DATA';
|
export const SET_EXTENSION_DATA = 'SET_EXTENSION_DATA';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PREFERENCES
|
||||||
|
* */
|
||||||
|
export const SET_USER_PREFERENCES = 'SET_USER_PREFERENCES';
|
||||||
@@ -1,31 +1,30 @@
|
|||||||
import cloneDeep from 'lodash.clonedeep';
|
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
// First tab
|
|
||||||
hotkeyDefinitions: [
|
|
||||||
// commandName, label, keys
|
|
||||||
// [{ zoom: { label: 'Zoom', keys: ['z'] }}]
|
|
||||||
],
|
|
||||||
// Second tab
|
|
||||||
windowLevelData: {
|
windowLevelData: {
|
||||||
// order, description, window (int), level (int)
|
1: { description: 'Soft tissue', window: '550', level: '40' },
|
||||||
// 0: { description: 'Soft tissue', window: '', level: '' },
|
2: { description: 'Lung', window: '150', level: '-600' },
|
||||||
|
3: { description: 'Liver', window: '150', level: '90' },
|
||||||
|
4: { description: 'Bone', window: '2500', level: '480' },
|
||||||
|
5: { description: 'Brain', window: '80', level: '40' },
|
||||||
|
6: { description: 'Trest', window: '1', level: '1' },
|
||||||
|
7: { description: '', window: '', level: '' },
|
||||||
|
8: { description: '', window: '', level: '' },
|
||||||
|
9: { description: '', window: '', level: '' },
|
||||||
|
10: { description: '', window: '', level: '' },
|
||||||
},
|
},
|
||||||
generalPreferences: {
|
generalPreferences: {
|
||||||
// language: 'en-US'
|
// language: 'en-US'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const preferences = (state, action) => {
|
const preferences = (state = defaultState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_USER_PREFERENCES': {
|
case 'SET_USER_PREFERENCES': {
|
||||||
const newState = action.state || cloneDeep(defaultState);
|
return Object.assign({}, state, action.state);
|
||||||
|
|
||||||
return Object.assign({}, state, newState);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return cloneDeep(state) || cloneDeep(defaultState);
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { defaultState };
|
||||||
export default preferences;
|
export default preferences;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Reducer } from 'redux-testkit';
|
||||||
|
|
||||||
|
import reducer, { defaultState } from './preferences';
|
||||||
|
import { SET_USER_PREFERENCES } from './../constants/ActionTypes.js';
|
||||||
|
|
||||||
|
describe('preferences reducer', () => {
|
||||||
|
it('should return the initial state', () => {
|
||||||
|
expect(reducer(undefined, {})).toEqual(defaultState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set user preferences state and properly merge with current state', () => {
|
||||||
|
const initialState = defaultState;
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: SET_USER_PREFERENCES,
|
||||||
|
state: { generalPreferences: { language: 'es' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
windowLevelData: defaultState.windowLevelData,
|
||||||
|
generalPreferences: {
|
||||||
|
language: 'es',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toReturnState(expectedState);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,7 +8,7 @@ const servers = (state = defaultState, action) => {
|
|||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_STUDY_DATA':
|
case 'SET_STUDY_DATA':
|
||||||
const updatedStudyData = cloneDeep(state).studyData;
|
const updatedStudyData = cloneDeep(state).studyData;
|
||||||
updatedStudyData[action.studyInstanceUid] = action.data;
|
updatedStudyData[action.StudyInstanceUID] = action.data;
|
||||||
|
|
||||||
return Object.assign({}, state, { studyData: updatedStudyData });
|
return Object.assign({}, state, { studyData: updatedStudyData });
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const DEFAULT_STATE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take the new number of rows and columns, delete all not used viewport data and also set
|
* Take the new number of Rows and Columns, delete all not used viewport data and also set
|
||||||
* active viewport as default in case current one is not available anymore.
|
* active viewport as default in case current one is not available anymore.
|
||||||
*
|
*
|
||||||
* @param {Number} numRows
|
* @param {Number} numRows
|
||||||
@@ -49,7 +49,7 @@ const findActiveViewportSpecificData = (
|
|||||||
return viewportSpecificData;
|
return viewportSpecificData;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Take new number of rows and columns and make sure the current active viewport index is still available, if not, return the default
|
* Take new number of Rows and Columns and make sure the current active viewport index is still available, if not, return the default
|
||||||
*
|
*
|
||||||
* @param {Number} numRows
|
* @param {Number} numRows
|
||||||
* @param {Number} numColumns
|
* @param {Number} numColumns
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import guid from '../../utils/guid';
|
|||||||
*
|
*
|
||||||
* @typedef {Object} Measurement
|
* @typedef {Object} Measurement
|
||||||
* @property {number} id -
|
* @property {number} id -
|
||||||
* @property {string} sopInstanceUID -
|
* @property {string} sopInstanceUid -
|
||||||
* @property {string} frameOfReferenceUID -
|
* @property {string} FrameOfReferenceUID -
|
||||||
* @property {string} referenceSeriesUID -
|
* @property {string} referenceSeriesUID -
|
||||||
* @property {string} label -
|
* @property {string} label -
|
||||||
* @property {string} description -
|
* @property {string} description -
|
||||||
@@ -30,8 +30,8 @@ import guid from '../../utils/guid';
|
|||||||
/* Measurement schema keys for object validation. */
|
/* Measurement schema keys for object validation. */
|
||||||
const MEASUREMENT_SCHEMA_KEYS = [
|
const MEASUREMENT_SCHEMA_KEYS = [
|
||||||
'id',
|
'id',
|
||||||
'sopInstanceUID',
|
'SOPInstanceUID',
|
||||||
'frameOfReferenceUID',
|
'FrameOfReferenceUID',
|
||||||
'referenceSeriesUID',
|
'referenceSeriesUID',
|
||||||
'label',
|
'label',
|
||||||
'description',
|
'description',
|
||||||
@@ -197,7 +197,11 @@ class MeasurementService {
|
|||||||
this.mappings[source.id] = [mapping];
|
this.mappings[source.id] = [mapping];
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`New measurement mapping added to source '${this._getSourceInfo(source)}'.`);
|
log.info(
|
||||||
|
`New measurement mapping added to source '${this._getSourceInfo(
|
||||||
|
source
|
||||||
|
)}'.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,11 +223,18 @@ class MeasurementService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapping = this._getMappingByMeasurementSource(measurementId, definition);
|
const mapping = this._getMappingByMeasurementSource(
|
||||||
|
measurementId,
|
||||||
|
definition
|
||||||
|
);
|
||||||
if (mapping) return mapping.toSourceSchema(measurement, definition);
|
if (mapping) return mapping.toSourceSchema(measurement, definition);
|
||||||
|
|
||||||
const measurement = this.getMeasurement(measurementId);
|
const measurement = this.getMeasurement(measurementId);
|
||||||
const matchingMapping = this._getMatchingMapping(source, definition, measurement);
|
const matchingMapping = this._getMatchingMapping(
|
||||||
|
source,
|
||||||
|
definition,
|
||||||
|
measurement
|
||||||
|
);
|
||||||
|
|
||||||
if (matchingMapping) {
|
if (matchingMapping) {
|
||||||
log.info('Matching mapping found:', matchingMapping);
|
log.info('Matching mapping found:', matchingMapping);
|
||||||
@@ -249,12 +260,15 @@ class MeasurementService {
|
|||||||
const sourceInfo = this._getSourceInfo(source);
|
const sourceInfo = this._getSourceInfo(source);
|
||||||
|
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
|
console.log('TEST');
|
||||||
log.warn('No source definition provided. Exiting early.');
|
log.warn('No source definition provided. Exiting early.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._sourceHasMappings(source)) {
|
if (!this._sourceHasMappings(source)) {
|
||||||
log.warn(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`);
|
log.warn(
|
||||||
|
`No measurement mappings found for '${sourceInfo}' source. Exiting early.`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +285,10 @@ class MeasurementService {
|
|||||||
/* Assign measurement source instance */
|
/* Assign measurement source instance */
|
||||||
measurement.source = source;
|
measurement.source = source;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Failed to map '${sourceInfo}' measurement for definition ${definition}:`, error.message);
|
log.warn(
|
||||||
|
`Failed to map '${sourceInfo}' measurement for definition ${definition}:`,
|
||||||
|
error.message
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,13 +312,24 @@ class MeasurementService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.measurements[internalId]) {
|
if (this.measurements[internalId]) {
|
||||||
log.info(`Measurement already defined. Updating measurement.`, newMeasurement);
|
log.info(
|
||||||
|
`Measurement already defined. Updating measurement.`,
|
||||||
|
newMeasurement
|
||||||
|
);
|
||||||
this.measurements[internalId] = newMeasurement;
|
this.measurements[internalId] = newMeasurement;
|
||||||
this._broadcastChange(this.EVENTS.MEASUREMENT_UPDATED, source, newMeasurement);
|
this._broadcastChange(
|
||||||
|
this.EVENTS.MEASUREMENT_UPDATED,
|
||||||
|
source,
|
||||||
|
newMeasurement
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.info(`Measurement added.`, newMeasurement);
|
log.info(`Measurement added.`, newMeasurement);
|
||||||
this.measurements[internalId] = newMeasurement;
|
this.measurements[internalId] = newMeasurement;
|
||||||
this._broadcastChange(this.EVENTS.MEASUREMENT_ADDED, source, newMeasurement);
|
this._broadcastChange(
|
||||||
|
this.EVENTS.MEASUREMENT_ADDED,
|
||||||
|
source,
|
||||||
|
newMeasurement
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newMeasurement.id;
|
return newMeasurement.id;
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ describe('MeasurementService.js', () => {
|
|||||||
measurementData: {},
|
measurementData: {},
|
||||||
};
|
};
|
||||||
measurement = {
|
measurement = {
|
||||||
sopInstanceUID: '123',
|
SOPInstanceUID: '123',
|
||||||
frameOfReferenceUID: '1234',
|
FrameOfReferenceUID: '1234',
|
||||||
referenceSeriesUID: '12345',
|
referenceSeriesUID: '12345',
|
||||||
label: 'Label',
|
label: 'Label',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
@@ -203,7 +203,6 @@ describe('MeasurementService.js', () => {
|
|||||||
const measurements = measurementService.getMeasurements();
|
const measurements = measurementService.getMeasurements();
|
||||||
|
|
||||||
expect(measurements.length).toEqual(2);
|
expect(measurements.length).toEqual(2);
|
||||||
expect(measurements.length).toEqual(2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,7 +261,7 @@ describe('MeasurementService.js', () => {
|
|||||||
|
|
||||||
source.addOrUpdate(definition, measurement);
|
source.addOrUpdate(definition, measurement);
|
||||||
|
|
||||||
expect(log.error.mock.calls.length).toBe(1);
|
expect(log.warn.mock.calls.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds new measurement with custom id', () => {
|
it('adds new measurement with custom id', () => {
|
||||||
@@ -278,7 +277,9 @@ describe('MeasurementService.js', () => {
|
|||||||
|
|
||||||
/* Add new measurement */
|
/* Add new measurement */
|
||||||
source.addOrUpdate(definition, newMeasurement);
|
source.addOrUpdate(definition, newMeasurement);
|
||||||
const savedMeasurement = measurementService.getMeasurement(newMeasurement.id);
|
const savedMeasurement = measurementService.getMeasurement(
|
||||||
|
newMeasurement.id
|
||||||
|
);
|
||||||
|
|
||||||
/* Clear dynamic data */
|
/* Clear dynamic data */
|
||||||
delete newMeasurement.modifiedTimestamp;
|
delete newMeasurement.modifiedTimestamp;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
const name = 'UINotificationService';
|
const name = 'UINotificationService';
|
||||||
|
|
||||||
|
const serviceShowRequestQueue = [];
|
||||||
|
|
||||||
const publicAPI = {
|
const publicAPI = {
|
||||||
name,
|
name,
|
||||||
hide: _hide,
|
hide: _hide,
|
||||||
@@ -21,7 +23,11 @@ const publicAPI = {
|
|||||||
|
|
||||||
const serviceImplementation = {
|
const serviceImplementation = {
|
||||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
_show: showArguments => {
|
||||||
|
serviceShowRequestQueue.push(showArguments);
|
||||||
|
|
||||||
|
console.warn('show() NOT IMPLEMENTED');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +82,11 @@ function setServiceImplementation({
|
|||||||
}
|
}
|
||||||
if (showImplementation) {
|
if (showImplementation) {
|
||||||
serviceImplementation._show = showImplementation;
|
serviceImplementation._show = showImplementation;
|
||||||
|
|
||||||
|
while (serviceShowRequestQueue.length > 0) {
|
||||||
|
const showArguments = serviceShowRequestQueue.pop();
|
||||||
|
serviceImplementation._show(showArguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ const INFO = Symbol('INFO');
|
|||||||
export default function getSeriesInfo(series) {
|
export default function getSeriesInfo(series) {
|
||||||
let info = series[INFO];
|
let info = series[INFO];
|
||||||
if (!info) {
|
if (!info) {
|
||||||
const modality = DICOMWeb.getString(series['00080060'], '').toUpperCase();
|
const Modality = DICOMWeb.getString(series['00080060'], '').toUpperCase();
|
||||||
info = Object.freeze({
|
info = Object.freeze({
|
||||||
modality,
|
Modality,
|
||||||
isLowPriority: isLowPriorityModality(modality),
|
isLowPriority: isLowPriorityModality(Modality),
|
||||||
seriesInstanceUid: DICOMWeb.getString(series['0020000E']),
|
SeriesInstanceUID: DICOMWeb.getString(series['0020000E']),
|
||||||
seriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0,
|
SeriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0,
|
||||||
});
|
});
|
||||||
series[INFO] = info;
|
series[INFO] = info;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
* Overridable namespace to allow getting study boxes data externally.
|
* Overridable namespace to allow getting study boxes data externally.
|
||||||
*
|
*
|
||||||
* The function must handle the first parameter as a studyInformation object containing at least the
|
* The function must handle the first parameter as a studyInformation object containing at least the
|
||||||
* studyInstanceUid attribute.
|
* StudyInstanceUID attribute.
|
||||||
*
|
*
|
||||||
* Shall return a promise that will be resolved with an object containing those attributes:
|
* Shall return a promise that will be resolved with an object containing those attributes:
|
||||||
* - studyInstanceUid {String}: copy of studyInformation.studyInstanceUid
|
* - StudyInstanceUID {String}: copy of studyInformation.StudyInstanceUID
|
||||||
* - modalities {String}: 2 uppercase letters for each modality split by any non-alphabetical char(s)
|
* - modalities {String}: 2 uppercase letters for each Modality split by any non-alphabetical char(s)
|
||||||
* - studyDate {String}: date formatted as YYYYMMDD
|
* - StudyDate {String}: date formatted as YYYYMMDD
|
||||||
* - studyDescription {String}: study description string
|
* - StudyDescription {String}: study description string
|
||||||
*/
|
*/
|
||||||
// TODO: What is this for?
|
// TODO: What is this for?
|
||||||
const getStudyBoxData = false;
|
const getStudyBoxData = false;
|
||||||
|
|||||||
Loaded 100 of 239 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user